Belajar Django: Views
Sebelumnya saya mau bilang dulu, tulisan ini tujuan utamanya untuk mengetahui alur django dari request user ke views ke models dan mengembalikan response berupa json ke user. Dalam tulisan ini juga akan banyak aturan yang dilanggar.
Dalam tulisan sebelumya sudah disebutkan bahwa views di django bertindak selayaknya controller, user agar bisa berinteraksi dengan views pasti memanfaatkan http request, misal saaat mengunjungi halaman `blog/` di url maka akan diarahkan ke “index” views blog post misalnya.
Hello World
Pertama buka berkas blog/views.py
lalu buat seperti ini
from django.shortcuts import render from django.http import JsonResponse def index(request): resp = {'hello': 'world'} return JsonResponse(resp)
Selanjutnya di dalam direktori “blog” buat satu berkas dengan nama `urls.py`, lalu isi seperti berikut
from django.urls import path from . import views app_name = 'blog' urlpatterns = [ path('', views.index, name='index'), ]
Daftarkan url “blog” ke projek di restdjango/urls.py
( restdjango bisa beda tergantung nama projek yang diberikan masing-masing ). Di dalam berkas tadi ubah jadi seperti ini
from django.contrib import admin from django.urls import path, include urlpatterns = [ path('blogs/', include('blog.urls')), path('admin/', admin.site.urls), ]
Penamaan `blogs/` di sana artinya bahwa url dengan prefix blogs akan mengacu ke blog/urls.py
.
Saat pertama kali membuat django projek ( tulisan pertama ) kita melihat halaman landing page django, namun setelah kita membuat url lainnya, halaman landing tidak akan ada lagi, dan saat kita coba akses `localhost/8000` yang ada malah halaman error 404. Itu dikarenakan django tidak mendeteksi adanya “route” terhadap “/”
Saya menggunakan Insomnia untuk mengetes urlnya, saat mengunjungi halaman localhost:8000/blogs
maka akan muncul:
{'hello': 'world'}
Menambahkan Halaman Lainnya
Di dalam blog/views.py
ubah menjadi berikut:
from django.shortcuts import render from django.http import JsonResponse def index(request): resp = {'hello': 'world'} return JsonResponse(resp) def create(request): resp = {'hello': 'create'} return JsonResponse(resp) def detail(request, post_id): resp = {'detail': post_id } return JsonResponse(resp) def update(request, post_id): resp = {'update': post_id} return JsonResponse(resp) def delete(request, post_id): resp = {'delete': post_id} return JsonResponse(resp)
Dan di `blog/urls.py` ubah menjadi berikut
urlpatterns = [ path('', views.index, name='index'), path('create', views.create, name='create'), path('<int:post_id>', views.detail, name='detail'), path('<int:post_id>/update', views.update, name='update'), path('<int:post_id>/delete', views.delete, name='delete'), ]
Url pattern di atas artinya
- akses blogs/ akan mengacu ke views method index
- blogs/create akan mengacu ke views method create
- blogs/1 akan mengacu ke method detail, dan nilai 1 akan dianggap sebagai post_id ( lihat method detail)
- blogs/1/update akan mengacu ke method update, dan nilai 1 akan dianggap sebagai post_id
- blogs/1/delete akan mengacu ke method delete, dan nilai 1 akan dianggap sebagai post_id
Di url pattern post_id diberi info sebagai integer, sehingga saat kita akses urlnya dan diganti dengan string seperti ‘blogs/something` yang muncul adalah halaman error karena pattern nya yang tidak sesuai
Silahkan coba kunjungi masih-masing url dan harusnya akan memunculkan pesan sesuai dengan resp
yang dibuat di views.
HTTP METHOD
Kode di atas semuanya masih berupa method GET tentu semua method GET rasanya tidak tepat, setiap fungsi umumnya harus sesuai dengan HTTP Method, mari ubah sedikit alur yang ada:
- Method GET ke blogs/ akan difungsikan sebagai menampilkan data
- Method POST ke blogs/ akan difungsikan sebagai menambahkan data
- Method GET ke blogs/id akan difungsikan sebagai menampilkan data id bersangkutan
- Method PUT ke blogs/id akan difungsikan sebagai mengubah data id bersangkutan
- Method DELETE ke blogs/id akan difungsikan sebagai proses mengahpus data id bersangkutan
Maka dari itu ubah `blog/views.py` menjadi berikut:
from django.shortcuts import render from django.http import JsonResponse from django.views.decorators.http import require_http_methods from django.views.decorators.csrf import csrf_exempt @require_http_methods(["GET", "POST"]) @csrf_exempt def index(request): if request.method == 'POST': resp = {'hello': 'create'} else: resp = {'hello': 'get'} return JsonResponse(resp) @require_http_methods(["GET", "PUT","DELETE"]) @csrf_exempt def detail(request, post_id): if request.method == 'GET': resp = {'detail': post_id } elif request.method == 'PUT': resp = {'update': post_id} else: resp = {'delete': post_id} return JsonResponse(resp)
Dan di `blog/urls.py` ubah menjadi
urlpatterns = [ path('', views.index, name='index'), path('<int:post_id>', views.detail, name='detail') ]
Mari bahas perubahan yang ada.
Pertama saya menambahkan decorator `http_request_method` fungsinya agar bisa membatasi satu method/fungsi sesuai dengan http method yang diberikan, dalam kode di atas di index misalnya, saya hanya mengijinkan method index diakses jika http method berupa POST dan GET.
Kedua, di sana ada csrf_exempt, sejujurnya saya rasa ini bukan cara yang baik, csrf itu salah satu perlinungan terhadap request form agar tidak sembarangan orang bisa melakukan request ke url post, namun karena ini untuk coba-coba dan agar tidak terkena pesan error csrf saya tambahkan decorator tersebut.
Terakhir, saya tambahkan pengecekan terhadap request method agar sesuai fungsinya masing-masing.
Sekarang coba kunjungi kembali url yang sudah dibuat dan sesuaikan http methodnya.
Tersambung Dengan Model
Di tulisan sebelumnya app yang dibuat sudah tersambung dengan admin, silahkan tambahkan beberapa data agar proses selanjutnya lebih “menyenangkan”
Menampilkan Semua data
Pertama kita import model ke dalam views, selanjutnya kita panggil di method index
from .models import Post @require_http_methods(["GET", "POST"]) @csrf_exempt def index(request): code = 200 if request.method == 'POST': resp = {'hello': 'create'} else: postList = Post.objects.order_by('-created_at').values() resp = {'data':list(postList)} return JsonResponse(resp, status=code)
Kalau kita coba akses halaman blogs/
maka akan muncul seperti berikut
{ "data": [ { "id": 6, "category_id_id": 1, "title": "some title", "content": "Some content", "publish": true, "created_at": "2019-05-26T05:34:48.909Z", "updated_at": "2019-05-26T05:34:48.909Z" }, { "id": 5, "category_id_id": 1, "title": "some title", "content": "Some content", "publish": true, "created_at": "2019-05-26T05:31:42.169Z", "updated_at": "2019-05-26T05:31:42.169Z" }, { "id": 4, "category_id_id": 2, "title": "Random", "content": "Randomsss", "publish": true, "created_at": "2019-05-25T17:10:17.596Z", "updated_at": "2019-05-25T17:10:17.596Z" }, { "id": 3, "category_id_id": 1, "title": "Computer 2", "content": "Computer 3", "publish": false, "created_at": "2019-05-25T17:09:49.974Z", "updated_at": "2019-05-25T17:09:49.975Z" }, { "id": 2, "category_id_id": 1, "title": "Some title", "content": "Content", "publish": true, "created_at": "2019-05-25T13:52:21.588Z", "updated_at": "2019-05-25T13:52:21.588Z" } ] }
Sekarang menambahkan data, di ubah view menjadi seperti berikut
... from .models import Post, Category @require_http_methods(["GET", "POST"]) @csrf_exempt def index(request): code = 200 if request.method == 'POST': try: title = request.POST['title'] content = request.POST['content'] published = request.POST['published'] category_id = request.POST['category_id'] cat = Category.objects.get(pk=category_id) create = Post(title=title, content=content, publish=published, category_id=cat) create.save() msg = '{} created'.format(create.title) except Exception as e: code = 400 msg = 'something went wrong with {}'.format(str(e)) resp = {'info': msg} else: postList = Post.objects.order_by('-created_at').values() resp = {'data':list(postList)} return JsonResponse(resp, status=code)
Kenapa dimunculkan objek category (cat) karena saat insert django meminta instance dari kateogri bukan sekadar id yang dibutuhkan, untuk mencobanya bisa seperti berikut
Untuk fitur lainya mirip-mirip, ubah method detail seperti berikut
... from django.http import JsonResponse, QueryDict ... @require_http_methods(["GET", "PUT","DELETE"]) @csrf_exempt def detail(request, post_id): code = 200 if request.method == 'GET': try: blog = Post.objects.filter(id=post_id).values()[0] except Exception as e: code = 404 blog = 'not found' resp = {'data': blog} elif request.method == 'PUT': try: put = QueryDict(request.body) title = put.get('title') content = put.get('content') published = put.get('published') category_id = put.get('category_id') cat = Category.objects.get(pk=category_id) current = Post.objects.get(pk=post_id) current.title = title current.content = content current.publish = published current.category_id_id = cat current.save() msg = '{} Updated'.format(title) except Exception as e: code = 400 msg = 'something went wrong with {}'.format(str(e)) resp = {'info': msg} else: try: blog = Post.objects.get(id=post_id) blog.delete() msg = 'Deleted' except Exception as e: code = 400 msg = 'something went wrong with {}'.format(str(e)) resp = {'delete': msg} return JsonResponse(resp, status=code)
yang sedikit menarik di atas adalah, walaupun django memiliki bisa mengecek http method tapi kita tidak bisa dengan mudah mengambil body content seperti `request.POST[‘title’]` menjadi request.PUT['title']
, perlu tambahan Querydict() untuk mengubah request body menjadi dictionary. Bisa dilihat di method PUT di atas.
Untuk Update bisa dicoba dengan insomnia seperti berikut:
Untuk view lengkapnya menjadi seperti berikut
from django.shortcuts import render from django.http import JsonResponse, QueryDict from django.views.decorators.http import require_http_methods from django.views.decorators.csrf import csrf_exempt from .models import Post, Category @require_http_methods(["GET", "POST"]) @csrf_exempt def index(request): code = 200 if request.method == 'POST': try: title = request.POST['title'] content = request.POST['content'] published = request.POST['published'] category_id = request.POST['category_id'] cat = Category.objects.get(pk=category_id) create = Post(title=title, content=content, publish=published, category_id=cat) create.save() msg = '{} created'.format(create.title) except Exception as e: code = 400 msg = 'something went wrong with {}'.format(str(e)) resp = {'info': msg} else: postList = Post.objects.order_by('-created_at').values() resp = {'data':list(postList)} return JsonResponse(resp, status=code) @require_http_methods(["GET", "PUT","DELETE"]) @csrf_exempt def detail(request, post_id): code = 200 if request.method == 'GET': try: blog = Post.objects.filter(id=post_id).values()[0] except Exception as e: code = 404 blog = 'not found' resp = {'data': blog} elif request.method == 'PUT': try: put = QueryDict(request.body) title = put.get('title') content = put.get('content') published = put.get('published') category_id = put.get('category_id') cat = Category.objects.get(pk=category_id) current = Post.objects.get(pk=post_id) current.title = title current.content = content current.publish = published current.category_id_id = cat current.save() msg = '{} Updated'.format(title) except Exception as e: code = 400 msg = 'something went wrong with {}'.format(str(e)) resp = {'info': msg} else: try: blog = Post.objects.get(id=post_id) blog.delete() msg = 'Deleted' except Exception as e: code = 400 msg = 'something went wrong with {}'.format(str(e)) resp = {'delete': msg} return JsonResponse(resp, status=code)
Note:
Tulisan ini sebenranya tulisan “maksa” agar bisa seolah-olah menyerupai REST, namun setelah saya googling untuk membuat rest dengan django banyak yang menyarankan untuk menggunakan Django Rest Framework (DRF), oleh karena itu selanjutnya akan berfokus di DRF.
Referensi:
https://docs.djangoproject.com/en/2.2/topics/http/decorators/#module-django.views.decorators.http
https://stackoverflow.com/questions/30243101/return-queryset-as-json/30243413
https://stackoverflow.com/questions/4994789/django-where-are-the-params-stored-on-a-put-delete-request