# **Python Web Development** ## 1. Welcome to Try Django 1.11 ### 1.1 Welcome to Try Django 1.11 Tujuan pengkodean untuk memecahkan masalah, dan Django merupakan cara terbaik untuk membangun aplikasi web akrena ditulis dalam Python yang mudah di akses. Django dapat digunakan untuk memecahkan berbagai masalah, mulai dari hal kecil seperti bebagai preferensi makanan hingga e-commerce dan pencocokan. Seri "**Trying Django 1.11**" ditujukan untuk pemula dan menyajikan dasar-dasar Django, sementara perubahan versi Django tidak signifikan untuk pemula. Tujuan materi ini ialah membangun proyek dan solusi secara cepat dan efektif menggunakan **Django**. ### 1.2 What is Django ? Django adalah perangkat lunak yang dapat Anda gunakan untuk mengembangkan aplikasi web dengan cepat dan efisien. Sebagian besar aplikasi web memiliki beberapa fungsi umum, seperti autentikasi, pengambilan informasi dari basis data, dan manajemen cookie. Developer harus mengodekan fungsionalitas serupa ke setiap aplikasi web yang mereka tulis. Django memudahkan pekerjaan mereka dengan mengelompokkan fungsi yang berbeda ke dalam kumpulan besar modul yang dapat digunakan kembali dan disebut kerangka kerja aplikasi web. Developer menggunakan kerangka kerja web Django untuk mengatur dan menulis kode mereka secara lebih efisien dan mengurangi waktu pengembangan web secara signifikan. ### 1.3 Walkthrough ![image](https://hackmd.io/_uploads/r1ARRCFAT.png) Aplikasi web ini memiliki beberapa fitur, termasuk pemahaman terhadap URL, tampilan informasi dari database, pencarian item, dan user dapat membandingkan kode mereka dengan kode dalam video. ![image](https://hackmd.io/_uploads/S1f5ykc06.png) Github adalah website yang berguna untuk real development. Semua peningkatan akan ditambahkan ke repository github, dan semua dapat mengakses code tersebut. ### 1.4 Getting Start with Django Pada Materi ini kita akan menggunakan Django di dalam Linux Ubuntu 20.04 LTS, berikut instalasinya: #### Instalasi 1. install Tools yang dibutuhkan beserta dependensi nya ``` sudo apt install python3.8 python3-virtualenv python3.8-venv python3-pip libreadline-gplv2-dev libncursesw5-dev libpq-dev python3-dev libssl-dev libsqlite3-dev tk-dev libgdbm-dev libc6-dev libbz2-dev ``` ![image](https://hackmd.io/_uploads/SyW75ZsCp.png) 2. Pastikan Python dan tools telah terinstall ``` $ which python3 /usr/bin/python3 $ which pip3 /usr/bin/pip ``` 3. Lihat semua package python ``` $ pip3 list ``` ![image](https://hackmd.io/_uploads/r1QQjZsAT.png) #### Create a Blank Django Project 1. Buat Direktori Development untuk virtualenv ``` $ mkdir Dev && cd Dev ``` 2. Buat Virtual environment ``` $ python3 -m venv trydjango-1.11 ``` 3. Aktifkan Virtual environment yang baru saja dibuat ``` $ cd trydjango-1.11/ $ source bin/activate ``` 4. Lihat seluruh package python didalam virtual environment ``` (trydjango-1.11)$ pip list ``` ![image](https://hackmd.io/_uploads/B1C10biC6.png) Note: Package di virtual environment berbeda dengan yang ada diluar virtual environment 5. Install Django ``` (trydjango-1.11)$ pip install django==1.11.* ``` ![image](https://hackmd.io/_uploads/HJNbAboA6.png) 6. Install Wheel apabila belum ter-install (opsional) ``` (trydjango-1.11)$ pip install wheel ``` 7. Buat project baru di direktori src ``` (trydjango-1.11)$ mkdir src && cd src (trydjango-1.11)$ django-admin startproject mywebsite ``` ![image](https://hackmd.io/_uploads/rkOmRWsRT.png) Note : **startproject** adalah perintah untuk membuat project **mywebsite** adalah nama dari direktori project 8. Lihat semua file project ``` (trydjango-1.11)$ cd .. (trydjango-1.11)$ tree src/ src/ ├── manage.py └── mywebsite ├── __init__.py ├── settings.py ├── urls.py └── wsgi.py ``` ![image](https://hackmd.io/_uploads/BJxw8WEiRa.png) #### Django Project Directory Structure **mywebsite/** adalah direktori root yang berisi semua file dari proyek tersebut. Nama direktori ini dapat diganti dengan apapun, karena hal itu tidak akan menjadi masalah bagi Django. manage.py adalah sebuah program untuk mengelola proyek Django. Sering kali menjalankan manage.py ketika ingin melakukan sesuatu dengan sebuah proyek, misalnya: menjalankan server, melakukan migrasi, membuat sesuatu, dll. **mywebsite/init.py** adalah sebuah file kosong yang menyatakan bahwa direktori ini adalah sebuah paket (python package) **mywebsite/settings.py** adalah tempat dimana kami mengkonfigurasi proyek tersebut. **mywebsite/urls.py** adalah tempat dimana kami mendeklarasikan URL **mywebsite/wsgi.py** adalah titik masuk untuk yang kompatibel dengan WSGI. #### Create a Blank Django Project(2) 1. Buat direktori baru didalam direktori mywebsite ``` (trydjango-1.11) ubuntu@ubunu2004:~/Dev/trydjango-1.11/src$ mkdir mywebsite/settings/ ``` 2. Buat 4 file baru didalam direktori settings ``` (trydjango-1.11) /src$ touch mywebsite/settings/__init__.py (trydjango-1.11) /src$ touch mywebsite/settings/base.py (trydjango-1.11) /src$ touch mywebsite/settings/production.py (trydjango-1.11) /src$ touch mywebsite/settings/local.py ``` 3. Copy file mywebsite/settings.py ke file mywebsite/settings/base.py ``` (trydjango-1.11)/src$ cp mywebsite/settings.py mywebsite/settings/base.py ``` 4. Edit file base.py di direktori settings, pastikan file memiliki konten seperti ini: ``` BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) ``` 5. Edit file mywebsite/setting/__init___.py ``` from .base import * from .production import * try: from .local import * except: pass ``` 6. Install dependensi lainnya ``` (trydjango-1.11)$ pip install psycopg2 (trydjango-1.11)$ pip install gunicorn (trydjango-1.11)$ pip install dj-database-url==0.4.2 (trydjango-1.11)$ pip install django-crispy-forms==1.6.1 (trydjango-1.11)$ pip install pillow ``` 7. Buat requirement.txt ``` (trydjango-1.11)/src$ pip freeze > requirements.txt ``` 8. Jalankan migrasi dan buat superuser ``` (trydjango-1.11)/src$ python manage.py migrate (trydjango-1.11)/src$ python manage.py createsuperuser ``` 9. Jalankan server ``` (trydjango-1.11)/src$ python manage.py runserver ``` 10. Cek di browser Linux kalian dengan mmembuka ` http://127.0.0.1:8000/`, untuk basis CLI, anda bisa me-remote VM dengan ssh, dan melakukan curl ke website tersebut, `curl http://127.0.0.1:8000/` Via GUI: ![image](https://hackmd.io/_uploads/rypxjXoA6.png) Via CLI: ![image](https://hackmd.io/_uploads/BkbGsmo06.png) 11. Copy base.py ke local.py dan production.py di direktori settings ``` (trydjango-1.11)/src$ cp mywebsite/settings/base.py mywebsite/settings/local.py (trydjango-1.11)/src$ cp mywebsite/settings/base.py mywebsite/settings/production.py ``` 12. Edit file production.py, dan ubah isi file menjadi seperti ini ``` DEBUG = False SECRET_KEY = "tambahkan random text atau nomor" ``` #### Start first our apps 1. Buat project baru di src dengan nama restaurants (jangan lupa mengaktifkan virtual environment `source bin/activate`) ``` python manage.py startapp restaurants #nama apps ``` 2. Ubah isi dari views.py di direktori restaurants/ ``` nano restaurants/views.py --- from django.http import HttpResponse from django.shortcuts import render # Create your views here. # function based view def home(request): return HttpResponse("hello") ``` ![image](https://hackmd.io/_uploads/rJSwrNsRT.png) 3. Import restaurants.views di file mywebsite/urls.py, sertakan urlpatterns nya ``` nano mywebsite/urls.py --- from django.conf.urls import url from django.contrib import admin from restaurants.views import home urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^$', home), ] ``` ![image](https://hackmd.io/_uploads/ByyArVsR6.png) 4. Nyalakan servernya dengan command `python manage.py runserver` dan akses ke localhost `http://127.0.0.1:8000/` di browser Ubuntu kalian. ![image](https://hackmd.io/_uploads/HkQGUEoCT.png) ## 2. HTML & Django ### 2.1 Rendering HTML Untuk Task sebelumnya, kata "hello" masih berupa plain text, dan itu bukan HTML, jadi pada materi ini, kita akan mempelajari penggunaan HTML dalam Django. 1. Masuk ke file views.py dalam direktori restaurants, jangan lupa untuk mengaktifkan Virtual environment di direktory trydjango-1.11 `source bin/activate` ``` nano src/restaurants/views.py ``` Yang awalnya seperti ini: ``` from django.http import HttpResponse from django.shortcuts import render # Create your views here. # function based view def home(request): return HttpResponse("hello") ``` Kita ubah menjadi seperti ini: ``` from django.http import HttpResponse from django.shortcuts import render # Create your views here. # function based view def home(request): html_ = """<!DOCTYPE html> <html lang=en> <head> <head/> <body> <h1>Hello world</h1> <p>This is html coming trough</p> </body> </html> """ return HttpResponse(html_) ``` Ini merubah kata "hello" yang berupa plain text, menjadi HTML. ![image](https://hackmd.io/_uploads/SJx4TIs06.png) 2. Ada fitur baru yaitu `f string` yang dimana, ia bisa mengganti text di html menjadi sebuah variabel.Contoh penggunaan `f string`: * kita ubah terlebih dahulu isi dari views.py tadi menjadi seperti ini: ``` from django.http import HttpResponse from django.shortcuts import render # Create your views here. # function based view def home(request): html_var = 'f strings' html_ = f"""<!DOCTYPE html> <html lang=en> <head> <head/> <body> <h1>Hello world</h1> <p>This is {html_var} coming trough</p> </body> </html> """ return HttpResponse(html_) ``` ini akan merubah {html_var} menjadi `f string` dikarenakan fitur python 3.6, hasilnya seperti berikut: ![image](https://hackmd.io/_uploads/Hkayp8jAp.png) ### 2.2 Render a Django Template Sekarang kita akan mencoba untuk menggunakan fitur templates. 1. Buat direktori templates dan file html di dalam direktori templates (Buat direktori templates sejajar dengan direktori mywebsites) ``` (trydjango-1.11)$ mkdir templates/ (trydjango-1.11)$ touch templates/base.html ``` 2. Edit file templates/base.html ``` nano templates/base.html --- <!DOCTYPE html> <html lang=en> <head> <head/> <body> <h1> Hello world </h1> <p> This is {html_var} coming trough </p> </body> </html> ``` 3. Edit base.py di dalam direktori settings ``` TEMPLATES = [ 'DIRS' : [os.path.join(BASE_DIR, '../templates')], ] ``` 4. Edit `mywebsite/settings/__init__.py`. Pastikan isinya sama seperti ini. ``` from .base import * #from .production import * #try: # from .local import * #except: # pass ``` 5. Tes browsing ke localhost ![image](https://hackmd.io/_uploads/H1Kct_nRp.png) ### 2.3 Render HTML Templates with python Variable 1. Edit `views.py` di direktori restaurants ``` import random from django.http import HttpResponse from django.shortcuts import render def home(request): num = random.randint(0, 100000000) return render(request, "base.html", {"html_var": True, "num": num}) ``` 2. Edit file `templates/base.html` ``` <!DOCTYPE html> <html lang=en> <head> <head/> <body> <h1> Hello world </h1> <p> This is {{ html_var }} coming trough </p> <p> Random number is {{ num }} </p> </body> </html> ``` 3. Test browsing ke localhost `http://127.0.0.1:8000` ![image](https://hackmd.io/_uploads/HyYCiO3Ca.png) ### 2.4 Using Context in Django Templates Disini kita akan menggunakan context, yang dimana kita bisa menggunakan 2 variabel dalam 1 waktu. 1. Edit `views.py` didalam direktori restaurants ``` import random from django.http import HttpResponse from django.shortcuts import render def home(request): num = None some_list = [ random.randint(0, 100000000), random.randint(0, 100000000), random.randint(0, 100000000) ] condition_bool_item = False #tergantung kondisi True atau False if condition_bool_item: num = random.randint(0, 100000000) context = { "num": num, "some_list": some_list } return render(request, "base.html", context) ``` 2. Edit file templataes/base.html ``` <!DOCTYPE html> <html lang=en> <head> <head/> <body> <h1> Hello world </h1> <p> This is <code>{% verbatim %}{{ html_var }}{% endverbatim %}</code> coming trough </p> <p> {% if num is not None %} Random number is {{ num }} {% endif %} </p> <p> {% for some_item in some_list %} {% if some_item|divisibleby:"2" %} Even number {% endif %} {{ some_item }}<br/> {% endfor %} </p> <p> Some item is {{ some_item }} </p> </body> </html> ``` 3. Test browsing, jika `condition_bool_item` adalah **False** ![image](https://hackmd.io/_uploads/r1sQuFnRa.png) 4. Test browsing, jika `condition_bool_item` adalah **True** ![image](https://hackmd.io/_uploads/B1XIOYh06.png) ### 2.5 Using inheritance template 1. Buat file baru di dalam direktori templates ``` (trydjango-1.11)$ touch templates/home.html (trydjango-1.11)$ touch templates/about.html (trydjango-1.11)$ touch templates/contact.html ``` 2. Edit `urls.py` didalam direktori `mywebsite` ``` from django.conf.urls import url from django.contrib import admin from restaurants.views import home, about, contact urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^$', home), url(r'^about/$', about), url(r'^contact/$', contact), ] ``` 3. Edit `views.py` di dalam direktori restaurants ``` import random from django.http import HttpResponse from django.shortcuts import render def home(request): num = None some_list = [ random.randint(0, 100000000), random.randint(0, 100000000), random.randint(0, 100000000) ] condition_bool_item = False if condition_bool_item: num = random.randint(0, 100000000) context = { "num": num, "some_list": some_list } return render(request, "home.html", context) def about(request): context = { } return render(request, "about.html", context) def contact(request): context = { } return render(request, "contact.html", context) ``` 4. Edit file `templates/base.html` ``` <!DOCTYPE html> <html lang=en> <head> <title>{% block head_tittle %}mcxxkim.com{% endblock head_tittle %}</title> <head> <body> <h1>mcxxkim.com</h1> <a href="/">Home</a> <a href="/about/">About</a> <a href="/contact/">Contact</a> <div class="container"> {% block content %}{% endblock content %} </div> </body> </html> ``` 5. Edit file `templates/home.html` ``` {% extends "base.html" %} {% block content %} <h1> Hello world </h1> <h3> {{ block.super }} </h3> <p> This is <code>{% verbatim %}{{ html_var }}{% endverbatim %}</code> coming trough </p> <p> {% if num is not None %} Random number is {{ num }} {% endif %} </p> <p> {% for some_item in some_list %} {% if some_item|divisibleby:"2" %} Even number {% endif %} {{ some_item }} <br/> {% endfor %} </p> <p> Some item is {{ some_item }} </p> {% endblock content %} ``` 6. Edit file `templates/about.html` ``` {% extends "base.html" %} {% block head_tittle %}About || {{ block.super }}{% endblock head_tittle %} {% block content %} <p>About</p> {% endblock %} ``` 7. Edit file `templates/contact.html` ``` {% extends "base.html" %} {% block head_tittle %}Contact || {{ block.super }}{% endblock head_tittle %} {% block content %} <p>Contact</p> {% endblock %} ``` 8. Tes di web browser, buka home page ![image](https://hackmd.io/_uploads/r1IaRY2Ra.png) 9. Tes di web browser, buka about page menggunakan hyperlink ![image](https://hackmd.io/_uploads/H1oe19hCp.png) 10. Tes di web browser, buka contact page menggunakan hyperlink ![image](https://hackmd.io/_uploads/rJA-1qhA6.png) **Note:** * {% block content %}{%endblock content%} : Digunakan untuk memungkinkan template inheritance, yang memungkinkan Anda untuk membagi template menjadi bagian-bagian yang dapat diganti atau diperluas oleh template turunannya. {% block content %} adalah contoh dari blok yang didefinisikan di dalam template utama yang kemudian dapat diisi oleh template turunan. * {% extends "base.html" %} digunakan untuk mengindikasikan bahwa template saat ini akan memperluas (inherit) dari template yang disebutkan, yaitu "base.html" dalam kasus ini. Ini berarti template saat ini akan menambahkan atau memperluas blok-blok yang didefinisikan dalam "base.html". Dengan menggunakan {% extends "base.html" %}, Anda mengambil struktur dasar dari "base.html" dan dapat menambahkan atau mengganti blok-blok tertentu sesuai kebutuhan. * {{ block.super }} adalah cara untuk merujuk ke konten blok dari template induk. Dalam konteks ini, jika Anda memiliki blok bernama 'content' di "base.html" dan ingin mempertahankan isinya serta menambahkan sesuatu di atas atau di bawahnya, Anda dapat menggunakan {{ block.super }} untuk menyisipkan konten blok dari template induk. * {% verbatim %} digunakan untuk menunjukkan bahwa konten di dalamnya harus ditampilkan secara harfiah tanpa diinterpretasikan oleh Django template engine. Ini bermanfaat jika Anda ingin menampilkan kode HTML, contoh kode, atau konten lainnya yang mungkin memiliki sintaksis atau tanda kurung ganda yang serupa dengan sintaksis Django. ### 2.6 Using template tag 1. Buat direktori baru dan file baru di dalam direktori templates ``` (trydjango-1.11)$ mkdir templates/snippets/ (trydjango-1.11)$ touch templates/snippets/nav.html (trydjango-1.11)$ touch templates/snippets/css.html (trydjango-1.11)$ touch templates/snippets/js.html (trydjango-1.11)$ touch templates/snippets/sidebar.html ``` 2. Edit file `templates/base.html` ``` <!DOCTYPE html> <html lang=en> <head> <title>{% block head_tittle %}mcxxkim.com{% endblock head_tittle %}</title> {% include 'snippets/css.html' %} <head> <body> {% include 'snippets/nav.html' %} <div class="container"> {% block content %}{% endblock content %} </div> {% include 'snippets/js.html' %} </body> </html> ``` 3. Edit file `nav.html` didalam direktori snippets ``` <div class="container"> <h1>mcxxkim.com</h1> <a href="/">Home</a> <a href="/about/">About</a> <a href="/contact/">Contact</a> </div> ``` 4. Edit `css.html` didalam direktori snippets. Anda dapat copy dari [Bootstrap CDN 3.3.7](https://blog.getbootstrap.com/2016/07/25/bootstrap-3-3-7-released/) ``` <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous"> ``` 5. Edit `js.html` didalam direktori snippets. Anda dapat copy dari [Jquery core 3.7.1 (minified)](https://releases.jquery.com/) ``` <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.j> ``` 6. Edit `sidebar.html` di dalam direktori snippets ``` <ul> <li>item 1</li> <li>item 2</li> <li>item 3</li> </ul> ``` 7. tambahkan `include tag` di `about.html` & `contact.html` ``` nano templates/about.html --- {% extends "base.html" %} {% block head_tittle %}About || {{ block.super }}{% endblock head_tittle %} {% block content %} <h1>About</h1> {% include 'snippets/sidebar.html' %} {% endblock %} ``` ``` nano templates/contact.html --- {% extends "base.html" %} {% block head_tittle %}Contact || {{ block.super }}{% endblock head_tittle %} {% block content %} <h1>Contact</h1> {% include 'snippets/sidebar.html' %} {% endblock %} ``` 8. Tes browsing, dan buka ke 3 halaman: a. Home page ![image](https://hackmd.io/_uploads/rJLJBc2C6.png) b. About page ![image](https://hackmd.io/_uploads/SJDerq3Ra.png) c. Contact page ![image](https://hackmd.io/_uploads/Hya-S5nRa.png) **NOTES:** * {% include 'snippets/css.html' %}: untuk menyisipkan (include) konten dari file yang disebutkan, yaitu 'css.html', ke dalam template HTML saat ini. Ini umumnya digunakan untuk menyertakan potongan kode yang sering digunakan, seperti file CSS, ke dalam template utama tanpa harus menuliskannya ulang setiap kali. Ini biasanya memudahkan kita untuk building website lebih cepat * css.html : digunakan untuk memuat file CSS tema Bootstrap. File ini berisi penyesuaian gaya tambahan dan memperluas tampilan, serta membuat tata letak responsif, dll. ini memudahkan kita untuk styling website. * js.html : Ini memiliki fungsi untuk menggunakan fungsionalitas JavaScript yang ditawarkan oleh jQuery dan Bootstrap dalam proyek. Ini memungkinkan Anda untuk membuat interaksi pengguna yang lebih dinamis dan menarik, serta memanfaatkan komponen Bootstrap seperti yang ditujukan. ### 2.7 Class based Views 1. Edit `views.py` di dalam direktori restaurants. ``` import random from django.http import HttpResponse from django.shortcuts import render from django.views import View def home(request): num = None some_list = [ random.randint(0, 100000000), random.randint(0, 100000000), random.randint(0, 100000000) ] condition_bool_item = False if condition_bool_item: num = random.randint(0, 100000000) context = { "num": num, "some_list": some_list } return render(request, "home.html", context) def about(request): context = { } return render(request, "about.html", context) def contact(request): context = { } return render(request, "contact.html", context) class ContactView(View): def get(self, request, *args, **kwargs): print(kwargs) context = { } return render(request, "contact.html", context) ``` **Notes:** * Disini hanya menambahkan `import View` dan `class ContactView(View)` * `print(kwargs)` akan menampilkan ID yang digunakan di terminal 2. Edit `urls.py` didalam direktori mywebsite ``` from django.conf.urls import url from django.contrib import admin from restaurants.views import home, about, contact, ContactView urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^$', home), url(r'^about/$', about), url(r'^contact/(?P<id>\d+)/$', ContactView.as_view()), ] ``` **Notes:** * Disini menambahkan import `ContactView` dan mengganti url contact. * `/(?P<id>\d+)/` berfungsi untuk merekam ID yang digunakan. 3. Tes browsing a. Mengakses tanpa menggunakan ID di browser `127.0.0.1:8000/contact/` ![image](https://hackmd.io/_uploads/H1K0Fq2Ap.png) b. Mengakses menggunakan ID `127.0.0.1:8000/contact/123/` ![image](https://hackmd.io/_uploads/BJ9e5cnR6.png) 4. Log dalam terminal akan terlihat seperti ini: ![image](https://hackmd.io/_uploads/rkJX593R6.png) ### 2.8 Template View Anda dapat menggunakan 2 metode dalam menggunakan **TemplateView** #### Metode 1 1. Edit `views.py` di dalam direktori restaurants ``` import random from django.http import HttpResponse from django.shortcuts import render from django.views import View from django.views.generic import TemplateView class HomeView(TemplateView): template_name = 'home.html' def get_context_data(self, *args, **kwargs): context = super(HomeView, self).get_context_data(*args, **kwargs) num = None some_list = [ random.randint(0, 100000000), random.randint(0, 100000000), random.randint(0, 100000000) ] condition_bool_item = False if condition_bool_item: num = random.randint(0, 100000000) context = { "num": num, "some_list": some_list } return context class AboutView(TemplateView): template_name = 'about.html' class ContactView(TemplateView): template_name = 'contact.html' ``` 2. Edit `urls.py` di dalam direktori mywebsite ``` from django.conf.urls import url from django.contrib import admin from restaurants.views import HomeView, AboutView, ContactView urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^$', HomeView.as_view()), url(r'^about/$', AboutView.as_view()), url(r'^contact/$', ContactView.as_view()), ] ``` **Notes:** * Pada materi view, tidak terlalu mengubah content dari website, hanya untuk simpelisasi code anda #### Metode 2 1. Edit `views.py` di dalam direktori restaurants ``` import random from django.http import HttpResponse from django.shortcuts import render from django.views import View from django.views.generic import TemplateView class HomeView(TemplateView): template_name = 'home.html' def get_context_data(self, *args, **kwargs): context = super(HomeView, self).get_context_data(*args, **kwargs) num = None some_list = [ random.randint(0, 100000000), random.randint(0, 100000000), random.randint(0, 100000000) ] condition_bool_item = False if condition_bool_item: num = random.randint(0, 100000000) context = { "num": num, "some_list": some_list } return context ``` 2. Edit `urls.py` di dalam direktori mywebsite ``` from django.conf.urls import url from django.contrib import admin from django.views.generic import TemplateView from restaurants.views import HomeView urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^$', HomeView.as_view()), url(r'^about/$', TemplateView.as_view(template_name='about.html')), url(r'^contact/$', TemplateView.as_view(template_name='contact.html')), ] ``` **Notes:** * Pada materi view, tidak terlalu mengubah content dari website, hanya untuk simpelisasi code anda * Perbedaan metode 1 dan 2, terdapat di penggunaan TemplateView. Metode 1 menggunakan TemplateView di file `views.py`. Metode 2, menggunakan TemplateView di file `views.py` dan `urls.py`. ## 3. Remembering Things ### 3.1 Remembering Things with Models Disini kita akan mempelajari cara menambahkan supeeruser baru, serta menambahkan menu di page admin 1. Buat superuser baru ``` (trydjango-1.11)$ python manage.py createsuperuser ``` 2. Akses Django admin di browser. Akses `127.0.0.1:8000/admin/`, dan login dengan superuser baru yang baru dibuat ![image](https://hackmd.io/_uploads/HyW_vpaRa.png) ![image](https://hackmd.io/_uploads/B1c9PapCT.png) Di bagian user, data di simpan di file db.sqlite3. File yang telah dibaut ketika memulai command migration. Lalu, kita akan menambahkan list favourite restaurant dengan mengedit `models.py` di direktori restaurants 3. Edit `models.py` di dalam direktori restaurants ``` from django.db import models class Restaurant(models.Model): name = models.CharField(max_length=120) location = models.CharField(max_length=120, null=True, blank=True) ``` 4. Edit `base.py` di dalam direktori settings. ``` INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'restaurants', ] ``` 5. Lakukan migrate restaurants models ``` (trydjango-1.11)$ python manage.py makemigrations (trydjango-1.11)$ python manage.py migrate ``` 6. Edit `admin.py` di dalam direktori restaurants ``` from django.contrib import admin from .models import Restaurant admin.site.register(Restaurant) ``` 7. Cek di browser ![image](https://hackmd.io/_uploads/ByKKdTaAp.png) Sekarang, kita bisa melihat menu Restaurants, setelah menambahkan menu favorit restaurant 8. Ubah nama modul, Edit nama modul di file `models.py` dan `admin.py` * models.py ``` from django.db import models class RestaurantLocation(models.Model): name = models.CharField(max_length=120) location = models.CharField(max_length=120, null=True, blank=True) ``` * admin.py ``` from django.contrib import admin from .models import RestaurantLocation admin.site.register(RestaurantLocation) ``` 9. Jalankan migrate setelah mengubah nama modul ``` (trydjango-1.11) ubuntu@ubunu2004:~/Dev/trydjango-1.11/src$ python manage.py makemigrations Did you rename the restaurants.Restaurant model to RestaurantLocation? [y/N] y (trydjango-1.11)$ python manage.py migrate ``` 10. Cek browser kalian ![image](https://hackmd.io/_uploads/Sy2HKaaCT.png) Seperti yang kita lihat, bagian nama telah berubah ![image](https://hackmd.io/_uploads/H1DuYaaAT.png) Anda dapat menambahkan lokasi restaurants disini. ### 3.2 More on Models field 1. Edit `models.py` di dalam direktori restaurants ``` from django.db import models class RestaurantLocation(models.Model): name = models.CharField(max_length=120) location = models.CharField(max_length=120, null=True, blank=True) category = models.CharField(max_length=120, null=True, blank=True) timestamp = models.DateTimeField(auto_now=False, auto_now_add=False) ``` 2. Jalankan migrate ``` (trydjango-1.11)$ python manage.py makemigrations You are trying to add a non-nullable field 'timestamp' to restaurantlocation without a default; we can't do that (the database needs something to populate existing rows). Please select a fix: 1) Provide a one-off default now (will be set on all existing rows with a null value for this column) 2) Quit, and let me add a default in models.py Select an option: 1 Please enter the default value now, as valid Python The datetime and django.utils.timezone modules are available, so you can do e.g. timezone.now Type 'exit' to exit this prompt >>> timezone.now Migrations for 'restaurants': restaurants/migrations/0003_auto_20240313_0626.py - Add field category to restaurantlocation - Add field timestamp to restaurantlocation (trydjango-1.11)$ python manage.py migrate ``` 3. Cek di browser ![image](https://hackmd.io/_uploads/SJo-eRaCT.png) ### 3.3 Displaying Saved Data 1. Edit `views.py` di dalam direktori restaurants ``` from django.http import HttpResponse from django.shortcuts import render from django.views import View from django.views.generic import TemplateView from .models import RestaurantLocation def restaurants_listview(request): template_name = 'restaurants/restaurants_list.html' queryset = RestaurantLocation.objects.all() context = { "object_list": queryset } return render(request, template_name, context) ``` 2. Edit `urls.py` di dalam direktori mywebsite ``` from django.conf.urls import url from django.contrib import admin from django.views.generic import TemplateView from restaurants.views import restaurants_listview urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^$', TemplateView.as_view(template_name='home.html')), url(r'^restaurants/$', restaurants_listview), url(r'^about/$', TemplateView.as_view(template_name='about.html')), url(r'^contact/$', TemplateView.as_view(template_name='contact.html')), ] ``` 3. Tambahkan folder dan file baru ``` (trydjango-1.11)$ mkdir -p restaurants/templates/restaurants (trydjango-1.11)$ touch restaurants/templates/restaurants/restaurants_list.html ``` 4. Edit `restaurants.list.html` ``` {% extends "base.html" %} {% block head_tittle %}Restaurants | {{ block.super }}{% endblock head_tittle %} {% block content %} <h1>Restaurants List</h1> <ul> {% for obj in object_list %} <li>{{ obj }} <br/> {{ obj.name }} {{ obj.location }} {{ obj.category }} {{ obj.timestamp }} {{ obj.update }}</li> {% endfor %} </ul> {% endblock %} ``` 5. Edit `models.py` di dalam direktori restaurants ``` from django.db import models # Create your models here. class RestaurantLocation(models.Model): name = models.CharField(max_length=120) location = models.CharField(max_length=120, null=True, blank=True) category = models.CharField(max_length=120, null=True, blank=True) timestamp = models.DateTimeField(auto_now=True) update = models.DateTimeField(auto_now_add=True) def __str__(self): return self.name ``` 6. Cek di browser. Akses `127.0.0.1:8000/restaurants` dan pastikan data sesuai. ![image](https://hackmd.io/_uploads/SyunNRaAp.png) ### 3.4 Understanding QuerySets 1. Akses Django shell ``` (trydjango-1.11)$ python manage.py shell Python 3.8.10 (default, Nov 22 2023, 10:22:35) [GCC 9.4.0] on linux Type "help", "copyright", "credits" or "license" for more information. (InteractiveConsole) >>> ``` 2. Coba melihat data didalam Database ``` >>> from restaurants.models import RestaurantLocation >>> RestaurantLocation.objects.all() <QuerySet [<RestaurantLocation: warkop>]> >>> for obj in RestaurantLocation.objects.all(): ... print(obj.name) ... warkop ``` 3. Filter dan update QuerySet ``` >>> qs = RestaurantLocation.objects.all() >>> qs.filter(category__iexact='warung') <QuerySet [<RestaurantLocation: warkop>]> >>> qs.update(category='cafe') 1 >>> qs <QuerySet [<RestaurantLocation: warkop>]> >>> qs.filter(category__iexact='cafe') <QuerySet [<RestaurantLocation: warkop>]> >>> ``` 4. Menambahkan Restaurants Location via shell ``` >>> obj = RestaurantLocation() >>> obj.name = "padang murah" >>> obj.location = "bandung" >>> obj.category = "warung makan" >>> obj.save() >>> qs <QuerySet [<RestaurantLocation: warkop>, <RestaurantLocation: padang murah>]> >>> ``` 5. Menambahkan Restaurants Location dengan 1 baris ``` >>> obj = RestaurantLocation.objects.create(name='bahari', location='bandung', category='warung makan') >>> >>> qs = RestaurantLocation.objects.all() >>> qs <QuerySet [<RestaurantLocation: warkop>, <RestaurantLocation: padang murah>, <RestaurantLocation: bahari>]> >>> ``` 6. Filtering data wigh exclude ``` >>> qs = RestaurantLocation.objects.filter(category__iexact='warung makan').exclude(name__icontains='bahari') >>> qs.count() 1 >>> qs <QuerySet [<RestaurantLocation: padang murah>]> >>> ``` 7. Keluar dari shell ``` >>> exit() (trydjango-1.11) ``` ### 3.5 Generic List View 1. Edit `views.py` di dalam direktori restaurants ``` from django.db.models import Q from django.http import HttpResponse from django.shortcuts import render, get_object_or_404 from django.views import View from django.views.generic import TemplateView, ListView, DetailView from .models import RestaurantLocation def restaurants_listview(request): template_name = 'restaurants/restaurants_list.html' queryset = RestaurantLocation.objects.all() context = { "object_list": queryset } return render(request, template_name, context) class RestaurantListView(ListView): def get_queryset(self): slug = self.kwargs.get("slug") if slug: queryset = RestaurantLocation.objects.filter( Q(category__iexact=slug) | Q(category__icontains=slug) ) else: queryset = RestaurantLocation.objects.all() return queryset ``` 2. Edit `urls.py` di dalam direktori mywebsite ``` from django.conf.urls import url from django.contrib import admin from django.views.generic import TemplateView from restaurants.views import ( restaurants_listview, RestaurantListView, ) urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^$', TemplateView.as_view(template_name='home.html')), url(r'^restaurants/$', RestaurantListView.as_view()), url(r'^restaurants/(?P<slug>\w+)/$', RestaurantListView.as_view()), url(r'^about/$', TemplateView.as_view(template_name='about.html')), url(r'^contact/$', TemplateView.as_view(template_name='contact.html')), ] ``` 3. Ubah nama restaurant_list.html ``` (trydjango-1.11)$ mv restaurants/templates/restaurants/restaurants_list.html restaurants/templates/restaurants/restaurantlocation_list.html ``` ### 3.6 Restaurant Profile detail (Lanjutan) 1. Edit views.py di dalam direktori restaurants directory ``` from django.db.models import Q from django.http import HttpResponse from django.shortcuts import render, get_object_or_404 from django.views import View from django.views.generic import TemplateView, ListView, DetailView from .models import RestaurantLocation def restaurants_listview(request): template_name = 'restaurants/restaurants_list.html' queryset = RestaurantLocation.objects.all() context = { "object_list": queryset } return render(request, template_name, context) class RestaurantListView(ListView): def get_queryset(self): slug = self.kwargs.get("slug") if slug: queryset = RestaurantLocation.objects.filter( Q(category__iexact=slug) | Q(category__icontains=slug) ) else: queryset = RestaurantLocation.objects.all() return queryset class RestaurantDetailView(DetailView): queryset = RestaurantLocation.objects.all() def get_object(self, *args, **kwargs): rest_id = self.kwargs.get('rest_id') obj = get_object_or_404(RestaurantLocation, id=rest_id) return obj ``` 2. Edit `urls.py` di dalam direktori mywebsite ``` from django.conf.urls import url from django.contrib import admin from django.views.generic import TemplateView from restaurants.views import ( restaurants_listview, RestaurantListView, RestaurantDetailView, ) urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^$', TemplateView.as_view(template_name='home.html')), url(r'^restaurants/$', RestaurantListView.as_view()), url(r'^restaurants/(?P<rest_id>\w+)/$', RestaurantDetailView.as_view()), url(r'^about/$', TemplateView.as_view(template_name='about.html')), url(r'^contact/$', TemplateView.as_view(template_name='contact.html')), ] ``` 3. Buat file baru di dalam template restaurant ``` (trydjango-1.11)$ touch restaurants/templates/restaurants/restaurantlocation_detail.html ``` 4. Edit file `restaurantlocation_detail.html` ``` {% extends "base.html" %} {% block head_tittle %}Restaurants | {{ block.super }}{% endblock head_tittle %} {% block content %} <h1>{{ object.name }} <small> {{ object.category }}</small></h1> <p>{{ object.location }}</p> <p>{{ object.timestamp }}, Updated {{ object.update|timesince }} ago</p> {% endblock %} ``` 5. Coba buka di browser ![image](https://hackmd.io/_uploads/SJXjARpRa.png) ### 3.7 SlugField & Unique Slug Generator 1. Edit `models.py` di dalam direktori restaurants ``` from django.db import models from django.db.models.signals import pre_save, post_save from .utils import unique_slug_generator # Create your models here. class RestaurantLocation(models.Model): name = models.CharField(max_length=120) location = models.CharField(max_length=120, null=True, blank=True) category = models.CharField(max_length=120, null=True, blank=True) timestamp = models.DateTimeField(auto_now=True) update = models.DateTimeField(auto_now_add=True) slug = models.SlugField(unique=True, null=True, blank=True) def __str__(self): return self.name @property def title(self): return self.name ``` 2. Migrate restaurants models ``` (trydjango-1.11)$ python manage.py makemigrations (trydjango-1.11)$ python manage.py migrate ``` 3. Buat file baru di dalam direktori restaurants ``` (trydjango-1.11)$ touch restaurants/utils.py ``` 4. Edit `utils,py` di dalam direktori restaurants ``` import random import string from django.utils.text import slugify def random_string_generator(size=10, chars=string.ascii_lowercase + string.digits): return ''.join(random.choice(chars) for _ in range(size)) def unique_slug_generator(instance, new_slug=None): if new_slug is not None: slug = new_slug else: slug = slugify(instance.title) Klass = instance.__class__ qs_exists = Klass.objects.filter(slug=slug).exists() if qs_exists: new_slug = "{slug}-{randstr}".format(slug=slug, randstr=random_string_generator(size=4)) return unique_slug_generator(instance, new_slug=new_slug) return slug ``` ### 3.8 Signal for Unique Slug (Lanjutan) 1. Edit `models.py` didalam direktori restaurants ``` from django.db import models from django.db.models.signals import pre_save, post_save from .utils import unique_slug_generator # Create your models here. class RestaurantLocation(models.Model): name = models.CharField(max_length=120) location = models.CharField(max_length=120, null=True, blank=True) category = models.CharField(max_length=120, null=True, blank=True) timestamp = models.DateTimeField(auto_now=True) update = models.DateTimeField(auto_now_add=True) slug = models.SlugField(unique=True, null=True, blank=True) def __str__(self): return self.name @property def title(self): return self.name def rl_pre_save_receiver(sender, instance, *args, **kwargs): print("saving...") print(instance.timestamp) if not instance.slug: instance.slug = unique_slug_generator(instance) def rl_post_save_receiver(sender, instance, created, *args, **kwargs): print('saved') print(instance.timestamp) pre_save.connect(rl_pre_save_receiver, sender=RestaurantLocation) post_save.connect(rl_post_save_receiver, sender=RestaurantLocation) ``` 2. Edit sesuatu di admin web, dan save ![image](https://hackmd.io/_uploads/ByRSEJ0Ra.png) 3. Lihat di terminal ![image](https://hackmd.io/_uploads/S1vDN1CR6.png) ## 4. Form, Views, Models, And More ### 4.1 Slug as URL params 1. Edit `urls.py` di direktori mywebsite ``` from django.conf.urls import url from django.contrib import admin from django.views.generic import TemplateView from restaurants.views import ( restaurants_listview, RestaurantListView, RestaurantDetailView, ) urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^$', TemplateView.as_view(template_name='home.html')), url(r'^restaurants/$', RestaurantListView.as_view()), url(r'^restaurants/(?P<slug>[\w-]+)/$', RestaurantDetailView.as_view()), url(r'^about/$', TemplateView.as_view(template_name='about.html')), url(r'^contact/$', TemplateView.as_view(template_name='contact.html')), ] ``` 2. Edit `views.py` di direktori restaurants ``` from django.db.models import Q from django.http import HttpResponse from django.shortcuts import render, get_object_or_404 from django.views import View from django.views.generic import TemplateView, ListView, DetailView from .models import RestaurantLocation def restaurants_listview(request): template_name = 'restaurants/restaurants_list.html' queryset = RestaurantLocation.objects.all() context = { "object_list": queryset } return render(request, template_name, context) class RestaurantListView(ListView): def get_queryset(self): slug = self.kwargs.get("slug") if slug: queryset = RestaurantLocation.objects.filter( Q(category__iexact=slug) | Q(category__icontains=slug) ) else: queryset = RestaurantLocation.objects.all() return queryset class RestaurantDetailView(DetailView): queryset = RestaurantLocation.objects.all() ``` 3. Edit `restaurantlocation_list.html` di direktori restaurants/template ``` {% extends "base.html" %} {% block head_tittle %}Restaurants | {{ block.super }}{% endblock head_tittle %} {% block content %} <h1>Restaurants List</h1> <ul> {% for obj in object_list %} <li><a href="/restaurants/{{ obj.slug }}/">{{ obj }}</a><br/> {{ obj.name }} {{ obj.location }} {{ obj.category }} {{ obj.timestamp }} {{ obj.update }}</li> {% endfor %} </ul> {% endblock %} ``` 4. Buka browser, saved semua data di database ![image](https://hackmd.io/_uploads/r14lXMgkR.png) 5. Akses `127.0.0.1:8000/restaurants` ![image](https://hackmd.io/_uploads/H1zzXGxyA.png) 6. Sekarang anda dapat mengklik tulisan warkop yang memiliki underline ![image](https://hackmd.io/_uploads/BJNNmfgk0.png) Dan slug akan di gunakan sebagai URL params ### 4.2 Saving Data The Hard & Wrong Way Secara singkat, metode ini menginput data dari formulir POST dan menyimpannya ke dalam database tanpa validasi. Hal ini membuka celah keamanan, karena data tidak diverifikasi dan dapat mengakibatkan masalah seperti manipulasi data oleh pihak yang tidak sah. Berikut adalah contoh membuat form nya: 1. Buat file `forms.py` di dalam direktori restaurants ``` from django import forms class RestaurantCreateForm(forms.Form): name = forms.CharField() location = forms.CharField(required=False) category = forms.CharField(required=False) ``` 2. Edit file `views.py` di dalam direktori restaurants ``` from django.db.models import Q from django.http import HttpResponseRedirect from django.shortcuts import render from django.views.generic import ListView, DetailView from .forms import RestaurantCreateForm from .models import RestaurantLocation def restaurant_createview(request): # if request.method == "GET": # print("get data") if request.method == "POST": # print("post data") # print(request.POST) title = request.POST.get("title") location = request.POST.get("location") category = request.POST.get("category") obj = RestaurantLocation.objects.create( name=title, location=location, category=category ) return HttpResponseRedirect("/restaurants/") template_name = 'restaurants/form.html' context = {} return render(request, template_name, context) def restaurants_listview(request): template_name = 'restaurants/restaurants_list.html' queryset = RestaurantLocation.objects.all() context = { "object_list": queryset } return render(request, template_name, context) class RestaurantListView(ListView): def get_queryset(self): slug = self.kwargs.get("slug") if slug: queryset = RestaurantLocation.objects.filter( Q(category__iexact=slug) | Q(category__icontains=slug) ) else: queryset = RestaurantLocation.objects.all() return queryset class RestaurantDetailView(DetailView): queryset = RestaurantLocation.objects.all() ``` 3. Buat `form.html` di dalam direktori template restaurants ``` {% extends "base.html" %} {% block head_title %}Add Restaurant | {{block.super}}{% endblock head_title %} {% block content %} <h1>Add Restaurant</h1> <form method='POST'> {% csrf_token %} <input type='text' name='title' placeholder="Restaurant"><br/> <input type='text' name='location' placeholder="Location"><br/> <input type='text' name='category' placeholder="Category"><br/> <button type='submit'>Save</button> </form> {% endblock %} ``` 4. Edit file `urls.py` pada direktori mywebsite ``` from django.conf.urls import url from django.contrib import admin from django.views.generic import TemplateView from restaurants.views import ( restaurants_listview, RestaurantListView, RestaurantDetailView, restaurant_createview, ) urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^$', TemplateView.as_view(template_name='home.html')), url(r'^restaurants/$', RestaurantListView.as_view()), url(r'^restaurants/create/$', restaurant_createview), url(r'^restaurants/(?P<slug>[\w-]+)/$', RestaurantDetailView.as_view()), url(r'^about/$', TemplateView.as_view(template_name='about.html')), url(r'^contact/$', TemplateView.as_view(template_name='contact.html')), ] ``` 5. Edit file `utils.py` didalam direktori restaurants ``` import random import string from django.utils.text import slugify DONT_USE = ['create'] def random_string_generator(size=10, chars=string.ascii_lowercase + string.digits): return ''.join(random.choice(chars) for _ in range(size)) def unique_slug_generator(instance, new_slug=None): if new_slug is not None: slug = new_slug else: slug = slugify(instance.title) if slug in DONT_USE: new_slug = "{slug}-{randstr}".format( slug=slug, randstr=random_string_generator(size=4) ) return unique_slug_generator(instance, new_slug=new_slug) Klass = instance.__class__ qs_exists = Klass.objects.filter(slug=slug).exists() if qs_exists: new_slug = "{slug}-{randstr}".format( slug=slug, randstr=random_string_generator(size=4)) return unique_slug_generator(instance, new_slug=new_slug) return slug ``` 6. Akses `127.0.0.1:8000/restaurants/create` ![image](https://hackmd.io/_uploads/S1IGE_l1C.png) 7. Apabila anda menambahkan Restaurant di form tersebut, data tersebut akan tersimpan di database ![image](https://hackmd.io/_uploads/HkoLNuxyC.png) Ketika meng-klik **Save**: ![image](https://hackmd.io/_uploads/SkLOVdgk0.png) ### 4.3 The Power of Django Forms Penggunaan Django Forms memudahkan validasi dan pengelolaan data pengguna dalam aplikasi web. Dibandingkan dengan pendekatan manual, Django Forms menawarkan kemudahan dan kekuatan validasi data. Formulir yang terhubung dengan model database dapat menangani validasi secara otomatis, termasuk validasi kustom. Ini mengurangi kode yang berulang dan meningkatkan efisiensi pengembangan. Secara keseluruhan, Django Forms mempercepat proses pengembangan aplikasi web sambil memastikan keamanan data. 1. Edit file `views.py` di direktori restaurants ``` from django.db.models import Q from django.http import HttpResponseRedirect from django.shortcuts import render from django.views.generic import ListView, DetailView from .forms import RestaurantCreateForm from .models import RestaurantLocation def restaurant_createview(request): form = RestaurantCreateForm(request.POST or None) errors = None if form.is_valid(): obj = RestaurantLocation.objects.create( name = form.cleaned_data.get('name'), location= form.cleaned_data.get('location'), category = form.cleaned_data.get('category') ) return HttpResponseRedirect("/restaurants/") if form.errors: errors = form.errors template_name = 'restaurants/form.html' context = {"form": form, "errors": errors} return render(request, template_name, context) def restaurants_listview(request): template_name = 'restaurants/restaurants_list.html' queryset = RestaurantLocation.objects.all() context = { "object_list": queryset } return render(request, template_name, context) class RestaurantListView(ListView): def get_queryset(self): slug = self.kwargs.get("slug") if slug: queryset = RestaurantLocation.objects.filter( Q(category__iexact=slug) | Q(category__icontains=slug) ) else: queryset = RestaurantLocation.objects.all() return queryset class RestaurantDetailView(DetailView): queryset = RestaurantLocation.objects.all() ``` 2. Edit file `form.html` di direktori template restaurants ``` {% extends "base.html" %} {% block head_title %}Add Restaurant | {{block.super}}{% endblock head_title %} {% block content %} <h1>Add Restaurant</h1> {{ errors}} <form method='POST'> {% csrf_token %} {{ form.as_p }} <button type='submit'>Save</button> </form> {% endblock %} ``` 3. Edit file `forms.py` di direktori restaurants ``` from django import forms class RestaurantCreateForm(forms.Form): name = forms.CharField() location = forms.CharField(required=False) category = forms.CharField(required=False) def clean_name(self): name = self.cleaned_data.get("name") if name == "Hello": raise forms.ValidationError("Not a valid name") return name ``` 4. Akses `127.0.0.1:8000/restaurants/create` pada browser, apa bila mengisi Name: Hello, akan terjadi error (Konfigurasi berhasil) ![image](https://hackmd.io/_uploads/Bk7Pwdg1A.png) ### 4.4 The Extra Power of Django Model Forms Django Model Forms memungkinkan pembuatan formulir secara otomatis berdasarkan model Django. Ini mengurangi kode yang harus ditulis dan memungkinkan validasi data sesuai aturan model. Model Forms juga mempermudah operasi CRUD pada model tanpa banyak usaha. Dengan fitur ini, pengembangan aplikasi Django menjadi lebih cepat, efisien, dan konsisten dengan model data yang ada. 1. Edit `forms.py` di dalam direktori restaurants ``` from django import forms from .models import RestaurantLocation class RestaurantCreateForm(forms.Form): name = forms.CharField() location = forms.CharField(required=False) category = forms.CharField(required=False) def clean_name(self): name = self.cleaned_data.get("name") if name == "Hello": raise forms.ValidationError("Not a valid name") return name class RestaurantLocationCreateForm(forms.ModelForm): class Meta: model = RestaurantLocation fields = [ 'name', 'location', 'category', ] def clean_name(self): name = self.cleaned_data.get("name") if name == "Hello": raise forms.ValidationError("Not a valid name") return name ``` 2. Edit `form.html` di dalam direktori templates restaurants ``` {% extends "base.html" %} {% block head_title %}Add Restaurant | {{block.super}}{% endblock head_title %} {% block content %} <h1>Add Restaurant</h1> {% if form.errors %} {{ form.errors }} {% endif %} <form method='POST'> {% csrf_token %} {{ form.as_p }} <button type='submit'>Save</button> </form> {% endblock %} ``` 3. Edit file `views.py` di dalam direktori restaurants ``` from django.db.models import Q from django.http import HttpResponseRedirect from django.shortcuts import render from django.views import View from django.views.generic import TemplateView, ListView, DetailView, CreateView from .forms import RestaurantCreateForm, RestaurantLocationCreateForm from .models import RestaurantLocation def restaurant_createview(request): form = RestaurantCreateForm(request.POST or None) errors = None if form.is_valid(): form.save() return HttpResponseRedirect("/restaurants/") if form.errors: errors = form.errors template_name = 'restaurants/form.html' context = {"form": form, "errors": errors} return render(request, template_name, context) def restaurants_listview(request): template_name = 'restaurants/restaurants_list.html' queryset = RestaurantLocation.objects.all() context = { "object_list": queryset } return render(request, template_name, context) class RestaurantListView(ListView): def get_queryset(self): slug = self.kwargs.get("slug") if slug: queryset = RestaurantLocation.objects.filter( Q(category__iexact=slug) | Q(category__icontains=slug) ) else: queryset = RestaurantLocation.objects.all() return queryset class RestaurantDetailView(DetailView): queryset = RestaurantLocation.objects.all() class RestaurantCreateView(CreateView): form_class = RestaurantLocationCreateForm template_name = 'restaurants/form.html' success_url = "/restaurants" ``` 4. Edit file `urls.py` di dalam direktori mywebsite ``` from django.conf.urls import url from django.contrib import admin from django.views.generic import TemplateView from restaurants.views import ( restaurants_listview, RestaurantListView, RestaurantDetailView, RestaurantCreateView ) urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^$', TemplateView.as_view(template_name='home.html')), url(r'^restaurants/$', RestaurantListView.as_view()), url(r'^restaurants/create/$', RestaurantCreateView.as_view()), url(r'^restaurants/(?P<slug>[\w-]+)/$', RestaurantDetailView.as_view()), url(r'^about/$', TemplateView.as_view(template_name='about.html')), url(r'^contact/$', TemplateView.as_view(template_name='contact.html')), ] ``` 5. Akses ke `127.0.0.1:8000/restaurants/create`, apabila setelah save direct ke laman `/restaurants` maka Konfigurasi berhasil. Coba juga menambahkan `Hello` pada kolom nama, apabila error seperti step sebelumnya, maka Konfigurasi berhasil. ![image](https://hackmd.io/_uploads/BkcYYuxJ0.png) ![image](https://hackmd.io/_uploads/SyDejdlkC.png) ![image](https://hackmd.io/_uploads/r1z-iue1C.png) ### 4.5 Simple + Effective Validation Validasi dalam Django memastikan keakuratan data sesuai aturan yang ditetapkan. Ini dilakukan dengan fitur bawaan atau validasi kustom. Validasi penting untuk keandalan data dan mencegah kerusakan di masa depan. Dengan metode validasi yang tepat, kualitas dan kinerja aplikasi dapat ditingkatkan. 1. Edit `forms.py` di direktori restaurants ``` from django import forms from .models import RestaurantLocation from .validators import validate_category class RestaurantCreateForm(forms.Form): name = forms.CharField() location = forms.CharField(required=False) category = forms.CharField(required=False) def clean_name(self): name = self.cleaned_data.get("name") if name == "Hello": raise forms.ValidationError("Not a valid name") return name class RestaurantLocationCreateForm(forms.ModelForm): category = forms.CharField(required=False, validators=[validate_category]) class Meta: model = RestaurantLocation fields = [ 'name', 'location', 'category', ] def clean_name(self): name = self.cleaned_data.get("name") if name == "Hello": raise forms.ValidationError("Not a valid name") return name ``` 2. Edit file `form.html` di direktori templates restaurants ``` {% extends "base.html" %} {% block head_title %}Add Restaurant | {{block.super}}{% endblock head_title %} {% block content %} <h1>Add Restaurant</h1> {% if form.errors.non_field_errors %} {{ form.errors.non_field_errors }} {% endif %} <form method='POST'> {% csrf_token %} {{ form.as_p }} <button type='submit'>Save</button> </form> {% endblock %} ``` 3. Buat file `validators.py` di dalam direktori restaurants ``` from django.core.exceptions import ValidationError def validate_even(value): if value %2 !=0: raise ValidationError( '%(value)s is not an even number', params={'value':value}, ) def validate_email(value): enail = value if ".edu" in email: raise ValidationError("We do not accept edu emails") CATEGORIES = ['Jawa', 'Sunda', 'Padang', 'Cafe'] def validate_category(value): cat = value.capitalize() if not value in CATEGORIES and not cat in CATEGORIES: raise ValidationError(f"{value} not a valid category") ``` 4. Edit file `models.py` di dalam direktori restaurants ``` from django.db import models # Create your models here. from django.db import models from django.db.models.signals import pre_save, post_save from .utils import unique_slug_generator from .validators import validate_category # Create your models here. class RestaurantLocation(models.Model): name = models.CharField(max_length=120) location = models.CharField(max_length=120, null=True, blank=True) category = models.CharField(max_length=120, null=True, blank=True, validators=[validate_category]) timestamp = models.DateTimeField(auto_now=True) update = models.DateTimeField(auto_now_add=True) slug = models.SlugField(unique=True, null=True, blank=True) def __str__(self): return self.name @property def title(self): return self.name def rl_pre_save_receiver(sender, instance, *args, **kwargs): instance.category = instance.category.capitalize() if not instance.slug: instance.slug = unique_slug_generator(instance) #def rl_post_save_receiver(sender, instance, created, *args, **kwargs): # print('saved') # print(instance.timestamp) # instance.save() pre_save.connect(rl_pre_save_receiver, sender=RestaurantLocation) #post_save.connect(rl_post_save_receiver, sender=RestaurantLocation) ``` 5. Lakukan migrate pada server (dikarenakan merubah models.py) ``` python manage.py makemigrations python manage.py migrate ``` **Note:** Jangan lupa untuk menjalankan server kembali ``` python manage.py runserver ``` 6. Akses `127.0.0.1:8000/restaurants/create` ![image](https://hackmd.io/_uploads/HkKWJYxk0.png) **Note**: Anda dapat menambahkan kategori dengan lowercase. Contoh disini yaitu: "jawa" 7. Apabila berhasil akan redirect ke `127.0.0.1:8000/restaurants` ![image](https://hackmd.io/_uploads/H1XP1KxkC.png) ### 4.6 Letting User Own Data Inti dari materi ini, kita memberi user kepemilikan atas data mereka sendiri. 1. Edit file `models.py` di dalam direktori Restaurants ``` from django.db import models # Create your models here. from django.conf import settings from django.db import models from django.db.models.signals import pre_save, post_save from .utils import unique_slug_generator from .validators import validate_category # Create your models here. User = settings.AUTH_USER_MODEL class RestaurantLocation(models.Model): owner = models.ForeignKey(User) name = models.CharField(max_length=120) location = models.CharField(max_length=120, null=True, blank=True) category = models.CharField(max_length=120, null=True, blank=True, validators=[validate_category]) timestamp = models.DateTimeField(auto_now=True) update = models.DateTimeField(auto_now_add=True) slug = models.SlugField(unique=True, null=True, blank=True) def __str__(self): return self.name @property def title(self): return self.name def rl_pre_save_receiver(sender, instance, *args, **kwargs): instance.category = instance.category.capitalize() if not instance.slug: instance.slug = unique_slug_generator(instance) #def rl_post_save_receiver(sender, instance, created, *args, **kwargs): # print('saved') # print(instance.timestamp) # instance.save() pre_save.connect(rl_pre_save_receiver, sender=RestaurantLocation) #post_save.connect(rl_post_save_receiver, sender=RestaurantLocation) ``` 2. Jalankan migrations dan migrate ``` python manage.py makemigrations python manage.py migrate ``` **Note:** Apabila terdapat pilihan, pilih nomor 1, dan setelah menekan 'ENTER' ketik nomor 1. 3. Jalankan Shell ``` python manage.py shell ``` **Note:** Pastikan kalian memakai user apa, disini adalah Ubuntu dan pastikan **active** 4. Jalankan perintah pada shell: ``` from django.contrib.auth import get_user_model User = get_user_model() User.objects.all() ubuntu_user = User.objects.get(id=1) ubuntu_user ubuntu_user.username ubuntu_user.email ubuntu_user.password ubuntu_user.is_active ubuntu_user.is_staff ubuntu_user obj = ubuntu_user obj instance = ubuntu_user instance instance.restaurantlocation_set.all() instance.restaurantlocation_set.filter(category__iexact='Jawa') User.objects.all() User.objects.get(id=2) ha_user = User.objects.get(id=2) ha_user.restaurantlocation_set.all() ``` 5. Keluar shell, lalu masuk ke shell lagi **Note:** Untuk keluar dari shell dapat mengunakan `CTRL + D` atau `exit()` ``` python manage.py shell ``` 6. Ketikan perintah berikut di shell ``` from restaurants.models import RestaurantLocation RestaurantLocation.objects.filter(owner__id=1) RestaurantLocation.objects.filter(owner__username__iexact='UBUNTU') qs = RestaurantLocation.objects.filter(owner__username__iexact='UBUNTU') qs obj = qs.first() obj.owner User = obj.owner.__class__ User User.objects.all() ubuntu_user = User.objects.all() ubuntu_user = User.objects.all().first() ubuntu_user.restaurantlocation_set.all() new_qs = ubuntu_user.restaurantlocation_set.all() new_obj = new_qs.first() new_obj RK = new_obj.__class__ RK.objects.all() ``` ### 4.7 Associate User to Form Data in FBV 1. Edit file views.py dalam direktori restaurants ``` from django.db.models import Q from django.http import HttpResponseRedirect from django.shortcuts import render from django.views import View from django.views.generic import TemplateView, ListView, DetailView, CreateView from .forms import RestaurantCreateForm, RestaurantLocationCreateForm from .models import RestaurantLocation def restaurant_createview(request): form = RestaurantCreateForm(request.POST or None) errors = None if form.is_valid(): if request.user.is_authenticated(): instance = form.save(commit=False) instance.owner = request.user instance.save() return HttpResponseRedirect("/restaurants/") else: return HttpResponseRedirect("/login/") if form.errors: errors = form.errors template_name = 'restaurants/form.html' context = {"form": form, "errors": errors} return render(request, template_name, context) def restaurants_listview(request): template_name = 'restaurants/restaurants_list.html' queryset = RestaurantLocation.objects.all() context = { "object_list": queryset } return render(request, template_name, context) class RestaurantListView(ListView): def get_queryset(self): slug = self.kwargs.get("slug") if slug: queryset = RestaurantLocation.objects.filter( Q(category__iexact=slug) | Q(category__icontains=slug) ) else: queryset = RestaurantLocation.objects.all() return queryset class RestaurantDetailView(DetailView): queryset = RestaurantLocation.objects.all() class RestaurantCreateView(CreateView): form_class = RestaurantLocationCreateForm template_name = 'restaurants/form.html' success_url = "/restaurants" ``` 2. Edit file `urls.py' di dalam direktori `mywebsite` ``` from django.conf.urls import url from django.contrib import admin from django.views.generic import TemplateView from restaurants.views import ( restaurant_createview, restaurants_listview, RestaurantListView, RestaurantDetailView, RestaurantCreateView ) urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^$', TemplateView.as_view(template_name='home.html')), url(r'^restaurants/$', RestaurantListView.as_view()), url(r'^restaurants/create/$', restaurant_createview ), #RestaurantCreateView.as_view()), url(r'^restaurants/(?P<slug>[\w-]+)/$', RestaurantDetailView.as_view()), url(r'^about/$', TemplateView.as_view(template_name='about.html')), url(r'^contact/$', TemplateView.as_view(template_name='contact.html')), ] ``` 3. Akses ke `127.0.0.1:8000/restaurants/create` dengan browser ![image](https://hackmd.io/_uploads/By5BDmMyR.png) 4. Apabila terdapat error seperti ini, maka cek ulang code anda! ![image](https://hackmd.io/_uploads/HySUwXM10.png) 5. Ketika save berhasil, cek pada restaurants yang barusan di tambahkan ![image](https://hackmd.io/_uploads/HJJevBzkC.png) **Notes:** lakukan cek dengan mengakses web dengan mode penyamaran (incognito) dan lakukan add restaurant. Apabila kondisinya error, maka konfigurasi telah berhasil ### 4.8 Login Required to View Pada materi ini, dijelaskan tentang cara mengatur tampilan agar memerlukan pengguna untuk masuk sebelum dapat mengaksesnya. 1. Edit file `base.py` di dalam direktori `mywebsite/settings`. Tambahkan `LOGIN_URL = '/login'` ![image](https://hackmd.io/_uploads/r1YG9Qf1A.png) 2. Edit file `urls.py` di dalam direktori mywebsite ``` from django.conf.urls import url from django.contrib import admin from django.views.generic import TemplateView from restaurants.views import ( restaurant_createview, restaurants_listview, RestaurantListView, RestaurantDetailView, RestaurantCreateView ) urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^$', TemplateView.as_view(template_name='home.html')), url(r'^restaurants/$', RestaurantListView.as_view()), url(r'^restaurants/create/$', RestaurantCreateView.as_view()), # url(r'^restaurants/(?P<slug>[\w-]+)/$', RestaurantDetailView.as_view()), url(r'^about/$', TemplateView.as_view(template_name='about.html')), url(r'^contact/$', TemplateView.as_view(template_name='contact.html')), ] ``` 3. Edit file `views.py` di dalam direktori restaurants ``` from django.contrib.auth.decorators import login_required from django.contrib.auth.mixins import LoginRequiredMixin from django.db.models import Q from django.http import HttpResponse, HttpResponseRedirect from django.shortcuts import render, get_object_or_404 from django.views import View from django.views.generic import TemplateView, ListView, DetailView, CreateView from .forms import RestaurantCreateForm, RestaurantLocationCreateForm from .models import RestaurantLocation @login_required(login_url='/login/') def restaurant_createview(request): form = RestaurantCreateForm(request.POST or None) errors = None if form.is_valid(): if request.user.is_authenticated(): instance = form.save(commit=False) instance.owner = request.user instance.save() return HttpResponseRedirect("/restaurants/") else: return HttpResponseRedirect("/login/") if form.errors: errors = form.errors template_name = 'restaurants/form.html' context = {"form": form, "errors": errors} return render(request, template_name, context) def restaurants_listview(request): template_name = 'restaurants/restaurants_list.html' queryset = RestaurantLocation.objects.all() context = { "object_list": queryset } return render(request, template_name, context) class RestaurantListView(ListView): def get_queryset(self): slug = self.kwargs.get("slug") if slug: queryset = RestaurantLocation.objects.filter( Q(category__iexact=slug) | Q(category__icontains=slug) ) else: queryset = RestaurantLocation.objects.all() return queryset class RestaurantDetailView(DetailView): queryset = RestaurantLocation.objects.all() class RestaurantCreateView(LoginRequiredMixin, CreateView): form_class = RestaurantLocationCreateForm login_url = '/login/' template_name = 'restaurants/form.html' success_url = "/restaurants" def form_valid(self, form): instance = form.save(commit=False) instance.owner = self.request.user return super(RestaurantCreateView, self).form_valid(form) ``` 4. Akses ke `127.0.0.1:8000/restaurants/create` dengan mode incognito (penyamaran) di browser ![image](https://hackmd.io/_uploads/B1T957zJA.png) ![image](https://hackmd.io/_uploads/SyPTqXM1A.png) Bisa terlihat bahwa ketika kita ketikkan dan enter, url akab berubah ke `127.0.0.1:8000/login/?next=restaurants/create` yang awalnya `127.0.0.1:8000/restaurants/create`. Ini menandakan konfigurasi berhasil ### 4.9 Login View Dalam materi ini, kita akan membuat page Login, yang di materi sebelumnya kita belum bisa mengakses page loginnya. 1. Buat file template `login.html` di dalam direktori registration yang dimana direktori registration berada di direktori templates ``` (trydjango-1.11) ubuntu@ubunu2004:~/Dev/trydjango-1.11/src$ cd templates/ (trydjango-1.11) ubuntu@ubunu2004:~/Dev/trydjango-1.11/src/templates$ mkdir registration (trydjango-1.11) ubuntu@ubunu2004:~/Dev/trydjango-1.11/src/templates$ cd registration/ (trydjango-1.11) ubuntu@ubunu2004:~/Dev/trydjango-1.11/src/templates/registration$ touch login.html ``` 2. Edit `login.html` seperti dibawah ini ``` {% extends "base.html" %} {% block content %} {% if form.errors %} <p>Your username and password didn't match. Please try again.</p> {% endif %} {% if next %} {% if user.is_authenticated %} <p>Your account doesn't have access to this page. To proceed, please login with an account that has access.</p> {% else %} <p>Please login to see this page.</p> {% endif %} {% endif %} <form method="post" action="{% url 'login' %}"> {% csrf_token %} <table> <tr> <td>{{ form.username.label_tag }}</td> <td>{{ form.username }}</td> </tr> <tr> <td>{{ form.password.label_tag }}</td> <td>{{ form.password }}</td> </tr> </table> <input type="submit" value="login" /> <input type="hidden" name="next" value="{{ next }}" /> </form> {# Assumes you setup the password_reset view in your URLconf #} {% endblock %} ``` 3. Edit file `urls.py` di dalam direktori mywebsite ``` from django.conf.urls import url from django.contrib import admin from django.views.generic import TemplateView from django.contrib.auth.views import LoginView from restaurants.views import ( restaurant_createview, restaurants_listview, RestaurantListView, RestaurantDetailView, RestaurantCreateView ) urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^$', TemplateView.as_view(template_name='home.html')), url(r'^login/$', LoginView.as_view(), name='login'), url(r'^restaurants/$', RestaurantListView.as_view()), url(r'^restaurants/create/$', RestaurantCreateView.as_view()), # url(r'^restaurants/(?P<slug>[\w-]+)/$', RestaurantDetailView.as_view()), url(r'^about/$', TemplateView.as_view(template_name='about.html')), url(r'^contact/$', TemplateView.as_view(template_name='contact.html')), ] ``` 4. Akses ke `127.0.0.1:8000/restaurant/create` di browser incognito. Defaultnya anda akan di arahkan ke page login, setelah anda login, baru bisa menambahkan restaurant ![image](https://hackmd.io/_uploads/Bk1CmEM1C.png) ![image](https://hackmd.io/_uploads/H1YAXNzyR.png) ### 4.10 Using Reverse to Shortcut URLS Disini kita akan menggunakan reverse untuk mempersingkat URL dalam Django. 1. Edit file `restaurantlocation_list.html` di dalam direktori `restaurants/templates/restaurants` ``` {% extends "base.html" %} {% block head_title %}Restaurants | {{ block.super }}{% endblock head_title %} {% block content %} <h1>Restaurants List</h1> <ul> {% for obj in object_list %} <li><a href='{{ obj.get_absolute_url }}'>{{ obj }}</a><br/> {{ obj.name }} {{ obj.location }} {{ obj.category }} {{ obj.timestamp }} </li> {% endfor %} </ul> {% endblock %} ``` 2. Buat file `urls.py` di dalam direktori restaurants ``` touch restaurants/urls.py nano restaurants/urls.py --- from django.conf.urls import url from .views import ( RestaurantListView, RestaurantDetailView, RestaurantCreateView ) urlpatterns = [ url(r'^$', RestaurantListView.as_view(), name='list'), url(r'^create/$', RestaurantCreateView.as_view(), name='create'), url(r'^(?P<slug>[\w-]+)/$', RestaurantDetailView.as_view(), name='detail'), ] ``` 3. Edit file `urls.py` di dalam direktori mywebsite ``` from django.conf.urls import url, include from django.contrib import admin from django.views.generic import TemplateView from django.contrib.auth.views import LoginView from restaurants.views import ( restaurant_createview, restaurants_listview, RestaurantListView, RestaurantDetailView, RestaurantCreateView ) urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^$', TemplateView.as_view(template_name='home.html'), name='home'), url(r'^login/$', LoginView.as_view(), name='login'), url(r'^restaurants/', include('restaurants.urls', namespace='restaurants')), url(r'^about/$', TemplateView.as_view(template_name='about.html'), name='about'), url(r'^contact/$', TemplateView.as_view(template_name='contact.html'), name='contact'), ] ``` 4. Edit file `nav.html` di dalam direktori `templates/snippets` ``` <div class="container"> <h1>mcxxkim.com</h1> <a href='{% url "home" %}'>Home</a> <a href='{% url "about" %}'>About</a> <a href='{% url "contact" %}'>Contact</a> <a href='{% url "restaurants:list" %}'>Restaurants</a> </div> ``` 5. Edit file `models.py` di dalam direktori restaurants ``` from django.db import models # Create your models here. from django.conf import settings from django.db import models from django.db.models.signals import pre_save, post_save from django.core.urlresolvers import reverse from .utils import unique_slug_generator from .validators import validate_category # Create your models here. User = settings.AUTH_USER_MODEL class RestaurantLocation(models.Model): owner = models.ForeignKey(User) name = models.CharField(max_length=120) location = models.CharField(max_length=120, null=True, blank=True) category = models.CharField(max_length=120, null=True, blank=True, validators=[validate_category]) timestamp = models.DateTimeField(auto_now=True) update = models.DateTimeField(auto_now_add=True) slug = models.SlugField(unique=True, null=True, blank=True) def __str__(self): return self.name def get_absolute_url(self): # return f"/restaurants/{self.slug}" return reverse('restaurants:detail', kwargs={'slug': self.slug}) @property def title(self): return self.name def rl_pre_save_receiver(sender, instance, *args, **kwargs): instance.category = instance.category.capitalize() if not instance.slug: instance.slug = unique_slug_generator(instance) #def rl_post_save_receiver(sender, instance, created, *args, **kwargs): # print('saved') # print(instance.timestamp) # instance.save() pre_save.connect(rl_pre_save_receiver, sender=RestaurantLocation) #post_save.connect(rl_post_save_receiver, sender=RestaurantLocation) ``` 6. Akses `127.0.0.1:8000` pada browser ![image](https://hackmd.io/_uploads/B1QGGHM10.png) **Note:** Akses satu persatu navbar (Home, About. Contact, Restaurant) dan lihat apakah page dapat terload, apabila page dapat diakses, maka konfigurasi berhasil ### 4.11 Menu Items App Kita akan menambahkan aplikasi baru bernama 'Menu Items'. Aplikasi ini membantu kita mendaftarkan makanan favorit tiap restoran. 1. Buat menus dengan command ``` (trydjango-1.11) ubuntu@ubunu2004:~/Dev/trydjango-1.11/src$ python manage.py startapp menus ``` 2. Edit file `models.py` pada direktori menus ``` from django.conf import settings from django.db import models from restaurants.models import RestaurantLocation class Item(models.Model): user = models.ForeignKey(settings.AUTH_USER_MODEL) restaurant = models.ForeignKey(RestaurantLocation) name = models.CharField(max_length=120) contents = models.TextField(help_text='Separate each item by comma') excludes = models.TextField(blank=True, null=True, help_text='Separate each item by comma') public = models.BooleanField(default=True) timestamp = models.DateTimeField(auto_now_add=True) updated = models.DateTimeField(auto_now=True) class Meta: ordering = ['-updated', '-timestamp'] def get_contents(self): return self.contents.split(",") def get_excludes(self): return self.excludes.split(",") ``` 3. Edit file `base.py` di dalam direktori `mywebsite/settings`. Tambahkan `menus` di bagian `INSTALLED_APPS` ``` INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'menus', 'restaurants', ] ``` ![image](https://hackmd.io/_uploads/HkW2SSGyC.png) 4. Edit file `admin.py` pada direktori menus ``` from django.contrib import admin from .models import Item admin.site.register(Item) ``` 5. Lakukan migrate dan jalankan server ``` python manage.py makemigrations python manage.py migrate python manage.py runserver ``` 6. Akses `127.0.0.1:8000/admin` pada browser ![image](https://hackmd.io/_uploads/BJ3eLBM1R.png) 7. Buka Items pada kolom `menus` lalu coba add items ![image](https://hackmd.io/_uploads/SJQN8rfkR.png) **Notes:** apabila sudah bisa membuka `add items` konfigurasi telah berhasil ### 4.12 Menu Items Views 1. Edit file views.py pada menus ``` from django.shortcuts import render from django.views.generic import ListView, DetailView, CreateView, UpdateView from .forms import ItemForm from .models import Item class ItemListView(ListView): def get_queryset(self): return Item.objects.filter(user=self.request.user) class ItemDetailView(DetailView): def get_queryset(self): return Item.objects.filter(user=self.request.user) class ItemCreateView(CreateView): template_name = 'form.html' form_class = ItemForm def form_valid(self, form): obj = form.save(commit=False) obj.user = self.request.user return super(ItemCreateView, self).form_valid(form) def get_queryset(self): return Item.objects.filter(user=self.request.user) def get_context_data(self, *args, **kwargs): context = super(ItemCreateView, self).get_context_data(*args, **kwargs) context['title'] = 'Create Item' return context class ItemUpdateView(UpdateView): template_name = 'form.html' form_class = ItemForm def get_queryset(self): return Item.objects.filter(user=self.request.user) def get_context_data(self, *args, **kwargs): context = super(ItemUpdateView, self).get_context_data(*args, **kwargs) context['title'] = 'Update Item' return context ``` 2. Buat file `forms.py` di dalam direktori menus dan edit filenya ``` from django import forms from .models import Item class ItemForm(forms.ModelForm): class Meta: model = Item fields = [ 'restaurant', 'name', 'contents', 'excludes', 'public', ] ``` 3. Edit file `models.py` di dalam direktori menus ``` from django.conf import settings from django.db import models from django.core.urlresolvers import reverse from restaurants.models import RestaurantLocation class Item(models.Model): user = models.ForeignKey(settings.AUTH_USER_MODEL) restaurant = models.ForeignKey(RestaurantLocation) name = models.CharField(max_length=120) contents = models.TextField(help_text='Separate each item by comma') excludes = models.TextField(blank=True, null=True, help_text='Separate each item by comma') public = models.BooleanField(default=True) timestamp = models.DateTimeField(auto_now_add=True) updated = models.DateTimeField(auto_now=True) def get_absolute_url(self): return reverse('menus:detail', kwargs={'pk': self.pk}) class Meta: ordering = ['-updated', '-timestamp'] def get_contents(self): return self.contents.split(",") def get_excludes(self): return self.excludes.split(",") ``` 4. Edit file `urls.py` di dalam direktori menus (bila tidak ada filenya, buat file baru) ``` from django.conf.urls import url from .views import ( ItemCreateView, ItemDetailView, ItemListView, ItemUpdateView, ) urlpatterns = [ url(r'^create/$', ItemCreateView.as_view(), name='create'), url(r'^(?P<pk>\d+)/$', ItemDetailView.as_view(), name='detail'), url(r'$', ItemListView.as_view(), name='List'), ] ``` 5. Edit file `urls.py` di dalam direktori mywebsite ``` from django.conf.urls import url, include from django.contrib import admin from django.views.generic import TemplateView from django.contrib.auth.views import LoginView from restaurants.views import ( restaurant_createview, restaurants_listview, RestaurantListView, RestaurantDetailView, RestaurantCreateView ) urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^$', TemplateView.as_view(template_name='home.html'), name='home'), url(r'^login/$', LoginView.as_view(), name='login'), url(r'^items/', include('menus.urls', namespace='menus')), url(r'^restaurants/', include('restaurants.urls', namespace='restaurants')), #url(r'^restaurants/$', RestaurantListView.as_view(), name='restaurants'), #url(r'^restaurants/create/$', RestaurantCreateView.as_view(), name='restaurants-creat'), #url(r'^restaurants/(?P<slug>[\w-]+)/$', RestaurantDetailView.as_view(), name='restaur'), url(r'^about/$', TemplateView.as_view(template_name='about.html'), name='about'), url(r'^contact/$', TemplateView.as_view(template_name='contact.html'), name='contact'), ] ``` 6. Buat direktori `menus` pada `src/templates` dan buat juga file `item_list.html` kemudian edit filenya ``` {% extends "base.html" %} {% block head_title %}Menu Items | {{ block.super }}{% endblock head_title %} {% block content %} <h1>Menu Items</h1> <ul> {% for obj in object_list %} <li><a href='{{ obj.get_absolute_url }}'>{{ obj.name }}</a><br/> <a href='/restaurants/{{ obj.slug }}/'>{{ obj }}</a><br/> {% if obj.contents %} <p>Contents:</p> <ul> {% for item in obj.get_contents %} <li>{{ item }}</li> {% endfor %} </ul> {% endif %} {% if obj.excludes %} <p>Excludes:</p> <ul> {% for item in obj.get_excludes %} <li>{{ item }}</li> {% endfor %} </ul> {% endif %} </li> {% endfor %} </ul> {% endblock %} ``` 7. Buat file `item_detail.html` pada direktori yang sama, edit filenya ``` {% extends "base.html" %} {% block head_title %}Menu Items | {{ block.super }}{% endblock head_title %} {% block content %} <h1>{{ object.name }}</h1> <p>Location: {{ object.restaurant.name }}</p> {% if object.contents %} <p>Contents:</p> <ul> {% for item in object.get_contents %} <li>{{ item }}</li> {% endfor %} </ul> {% endif %} {% if object.excludes %} <p>Excludes:</p> <ul> {% for item in object.get_excludes %} <li>{{ item }}</li> {% endfor %} </ul> {% endif %} {% endblock %} ``` 8. Buka browser Ubuntu dan coba tambahkan items di menu admin ![image](https://hackmd.io/_uploads/rknGFl4J0.png) 9. Lihat ke halaman items ![image](https://hackmd.io/_uploads/SkOwYe4JA.png) 10. Coba menambahkan item tanpa Django Admin ![image](https://hackmd.io/_uploads/rywKFlEk0.png) ### 4.13 Limiting Form Field to QuerySet 1. Edit file `views.py` di dalam direktori menus ``` from django.contrib.auth.mixins import LoginRequiredMixin from django.shortcuts import render from django.views.generic import ListView, DetailView, CreateView, UpdateView from .forms import ItemForm from .models import Item class ItemListView(ListView): def get_queryset(self): return Item.objects.filter(user=self.request.user) class ItemDetailView(DetailView): def get_queryset(self): return Item.objects.filter(user=self.request.user) class ItemCreateView(CreateView): template_name = 'form.html' form_class = ItemForm def form_valid(self, form): obj = form.save(commit=False) obj.user = self.request.user return super(ItemCreateView, self).form_valid(form) def get_form_kwargs(self): kwargs = super(ItemCreateView, self).get_form_kwargs() kwargs['user'] = self.request.user return kwargs def get_queryset(self): return Item.objects.filter(user=self.request.user) def get_context_data(self, *args, **kwargs): context = super(ItemCreateView, self).get_context_data(*args, **kwargs) context['title'] = 'Create Item' return context class ItemUpdateView(LoginRequiredMixin, UpdateView): template_name = 'form.html' form_class = ItemForm def get_queryset(self): return Item.objects.filter(user=self.request.user) def get_context_data(self, *args, **kwargs): context = super(ItemUpdateView, self).get_context_data(*args, **kwargs) context['title'] = 'Update Item' return context ``` 2. Edit file `forms.py` di dalam direktori menus ``` from django import forms from .models import Item from restaurants.models import RestaurantLocation class ItemForm(forms.ModelForm): class Meta: model = Item fields = [ 'restaurant', 'name', 'contents', 'excludes', 'public', ] def __init__(self, user=None, *args, **kwargs): print(user) print(kwargs) super(ItemForm, self).__init__(*args,**kwargs) self.fields['restaurant'].queryset = RestaurantLocation.objects.filter(owner=user).exclude(item__isnull=False) ``` 3. Edit file `urls.py` di dalam direktori menus ``` from django.conf.urls import url from .views import ( ItemCreateView, ItemDetailView, ItemListView, ItemUpdateView, ) urlpatterns = [ url(r'^create/$', ItemCreateView.as_view(), name='create'), url(r'^(?P<pk>\d+)/edit/$', ItemUpdateView.as_view(), name='update'), url(r'^(?P<pk>\d+)/$', ItemDetailView.as_view(), name='detail'), url(r'$', ItemListView.as_view(), name='List'), ] ``` 4. Akses ke `127.0.0.1:8000/items/1/edit` di browser ![image](https://hackmd.io/_uploads/S1G81WEyR.png) ### 4.14 Personalize Items Kita akan membuat fitur untuk user melihat dan mengelola item mereka sendiri. 1. Edit file `views.py` di dalam direktori restaurants ``` from django.contrib.auth.decorators import login_required from django.contrib.auth.mixins import LoginRequiredMixin from django.db.models import Q from django.http import HttpResponse, HttpResponseRedirect from django.shortcuts import render, get_object_or_404 from django.views import View from django.views.generic import TemplateView, ListView, DetailView, CreateView, UpdateView from .forms import RestaurantCreateForm, RestaurantLocationCreateForm from .models import RestaurantLocation class RestaurantListView(LoginRequiredMixin, ListView): def get_queryset(self): return RestaurantLocation.objects.filter(owner=self.request.user) class RestaurantDetailView(LoginRequiredMixin, DetailView): def get_queryset(self): return RestaurantLocation.objects.filter(owner=self.request.user) class RestaurantCreateView(LoginRequiredMixin, CreateView): form_class = RestaurantLocationCreateForm login_url = '/login/' template_name = 'form.html' #success_url = "/restaurants/" def form_valid(self, form): instance = form.save(commit=False) instance.owner = self.request.user return super(RestaurantCreateView, self).form_valid(form) def get_context_data(self, *args, **kwargs): context = super(RestaurantCreateView, self).get_context_data(*args, **kwargs) context['title'] = 'Add Restaurant' return context class RestaurantUpdateView(LoginRequiredMixin, UpdateView): form_class = RestaurantLocationCreateForm login_url = '/login/' template_name = 'restaurants/detail-update.html' #success_url = "/restaurants/" def get_context_data(self, *args, **kwargs): context = super(RestaurantUpdateView, self).get_context_data(*args, **kwargs) name = self.get_object().name context['title'] = f'Update Restaurant: {name}' return context def get_queryset(self): return RestaurantLocation.objects.filter(owner=self.request.user) ``` 2. Edit file `urls.py` di dalam direktori restaurants ``` from django.conf.urls import url from .views import ( RestaurantListView, RestaurantDetailView, RestaurantCreateView, RestaurantUpdateView ) urlpatterns = [ url(r'^create/$', RestaurantCreateView.as_view(), name='create'), # url(r'^(?P<slug>[\w-]+)/edit/$', RestaurantUpdateView.as_view(), name='edit'), url(r'^(?P<slug>[\w-]+)/$', RestaurantUpdateView.as_view(), name='detail'), url(r'$', RestaurantListView.as_view(), name='list'), ] ``` 3. Edit file `urls.py` di dalam direktori menus ``` from django.conf.urls import url from .views import ( ItemCreateView, ItemDetailView, ItemListView, ItemUpdateView, ) urlpatterns = [ url(r'^create/$', ItemCreateView.as_view(), name='create'), # url(r'^(?P<pk>\d+)/edit/$', ItemUpdateView.as_view(), name='edit'), url(r'^(?P<pk>\d+)/$', ItemUpdateView.as_view(), name='detail'), url(r'$', ItemListView.as_view(), name='list'), ] ``` 4. Edit `nav.html` di dalam direktori `templates/snippets` ``` <div class="container"> <h1>mcxxkim.com</h1> <a href='{% url "home" %}'>Home</a> <a href='{% url "about" %}'>About</a> <a href='{% url "contact" %}'>Contact</a> {% if request.user.is_authenticated %} <a href='{% url "restaurants:list" %}'>Restaurants</a> <a href='{% url "menus:list" %}'>Menu Items</a> {% endif %} </div> ``` 4. Buat file baru ``` touch restaurants/templates/restaurants/form.html touch restaurants/templates/restaurants/detail-update.html touch templates/menus/detail-update.html touch templates/snippets/form-snippets.html ``` 5. Edit `form.html` yang baru saja dibuat ``` {% extends "base.html" %} {% block head_title %}{{ title }} | {{block.super}}{% endblock head_title %} {% block content %} <h1>{{ title }}</h1> {% if form.errors.non_field_errors %} {{ form.errors.non_field_errors }} {% endif %} <form method='POST'> {% csrf_token %} {{ form.as_p }} <button type='submit'>Save</button> </form> {% endblock %} ``` 6. Edit `detail-update.html` yang baru saja dibuat (templates restaurants) ``` {% extends "base.html" %} {% block head_title %}{{ form.instance.title }} | Restaurants | {{ block.super }}{% end> {% block content %} <h1>{{ form.instance.title }} <small> {{ form.instance.category }}</small></h1> <p>{{ form.instance.location }}</p> <p>{{ form.instance.timestamp }}, Updated {{ form.instance.updated|timesince }} ago</p> <hr/> <h3>Make Changes</h3> {% include 'snippets/form-snippets.html' with form=form %} </div> </div> {% endblock %} ``` 7. Edit `detail-update.html` yang baru saja dibuat (templates menus) ``` {% extends "base.html" %} {% block head_title %}{{form.instance.name }} | Menu Items | {{ block.super }}{% endblock head_title %} {% block content %} <h1>{{ form.instance.name }}</h1> <p>Location: {{ form.instance.restaurant.name }}</p> {% if form.instance.contents %} <p>Contents:</p> <ul> {% for item in form.instance.get_contents %} <li>{{ item }}</li> {% endfor %} </ul> {% endif %} {% if form.instance.excludes %} <p>Excludes:</p> <ul> {% for item in form.instance.get_excludes %} <li>{{ item }}</li> {% endfor %} </ul> {% endif %} <hr/> {% include 'snippets/form-snippets.html' %} {% endblock %} ``` 8. Edit `form-snippets.html` yang baru saja dibuat (templates menus) ``` {% if form.errors.non_field_errors %} {{ form.errors.non_field_errors }} {% endif %} <form method='POST'> {% csrf_token %} {{ form.as_p }} <button type='submit'>Save</button> </form> ``` 9. Akses `127.0.0.1:8000` di browser kalian Disini anda dapat mengubah isi dari items dan restaurants dengan menekan nama items dan restaurants. Serta anda dapat menambahkan restaurants atau items baru dengan menekan 'add' di sebelah 'My restaurants' atau 'menu items'. Dan juga apabila user terindikasi belum login, maka navbar Items dan restaurants tidak akan keluar. ![image](https://hackmd.io/_uploads/Sy0dvjVyR.png) ![image](https://hackmd.io/_uploads/B1jFPoVJ0.png) ![image](https://hackmd.io/_uploads/HJ_5viEkC.png) Apabila belum login: ![image](https://hackmd.io/_uploads/r1ojwjN1C.png) ### 4.15 User Profile View Disini kita akan membuat fitur baru berupa "Profiles" untuk membuat profil publik. 1. Start project baru bernama profiles ``` python manage.py startapp profiles ``` 2. Edit `views.py` dalam direktori profiles ``` from django.contrib.auth import get_user_model from django.http import Http404 from django.shortcuts import render, get_object_or_404 from django.views.generic import DetailView User = get_user_model() class ProfileDetailView(DetailView): template_name = 'profiles/user.html' def get_object(self): username = self.kwargs.get("username") if username is None: raise Http404 return get_object_or_404(User, username__iexact=username, is_active=True) ``` 3. Buat file `urls.py` di dalam direktori profiles dan edit ``` touch profiles/urls.py nano profiles/urls.py --- from django.conf.urls import url from .views import ProfileDetailView urlpatterns = [ url(r'^(?P<username>[\w-]+)/$', ProfileDetailView.as_view(), name='detail'), ] ``` 4. Edit file `urls.py` di dalam direktori mywebsite ``` from django.conf.urls import url, include from django.contrib import admin from django.views.generic import TemplateView from django.contrib.auth.views import LoginView from restaurants.views import ( RestaurantListView, RestaurantDetailView, RestaurantCreateView ) urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^$', TemplateView.as_view(template_name='home.html'), name='home'), url(r'^login/$', LoginView.as_view(), name='login'), url(r'^u/', include('profiles.urls', namespace='profiles')), url(r'^items/', include('menus.urls', namespace='menus')), url(r'^restaurants/', include('restaurants.urls', namespace='restaurants')), url(r'^about/$', TemplateView.as_view(template_name='about.html'), name='about'), url(r'^contact/$', TemplateView.as_view(template_name='contact.html'), name='contact'), ] ``` 5. Edit file `base.py` di dalam direktori `mywebsite/settings` ![image](https://hackmd.io/_uploads/HytSpjVkC.png) 6. Buat direktori `/profiles` di dalam templates, dan buat file baru dalam direktori `templates/profiles` bernama `user.html` dan edit. ``` mkdir templates/profiles touch templates/profiles/user.html nano templates/profiles/user.html --- {% extends "base.html" %} {% block head_title %}{{ user.username }} | {{ block.super }}{% endblock head_title %} {% block content %} <h1>{{ user.username }}</h1> <hr/> {% if user.restaurantlocation_set.all %} {% for rest in user.restaurantlocation_set.all %} <li><b>{{ rest.title }}</b> {{ rest.location }} | {{ rest.category }}</li> <p> <b>Items</b> <ul> {% for item in rest.item_set.all %} <li style='margin-bottom: 15px;'>{{ item.name }}<br/> {% for ing in item.get_contents %} <span style='padding: 2px 4px; margin-right:4px; background-color: #ccc;'>{{ ing }}</sp> {% endfor %} </li> {% endfor %} </ul> </p> {% endfor %} {% else %} <p class='lead'>No Items Found</p> {% endif %} {% endblock %} ``` **Note:** Untuk Tutorial selanjutnya dapat mengakses link berikut: [Python Web Development Part 2](https://hackmd.io/@ZNLLPEerQgmDKfOJZIGVKQ/H1ACToE10)