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

  1. akses blogs/ akan mengacu ke views method index
  2. blogs/create akan mengacu ke views method create
  3. blogs/1 akan mengacu ke method detail, dan nilai 1 akan dianggap sebagai post_id ( lihat method detail)
  4. blogs/1/update akan mengacu ke method update, dan nilai 1 akan dianggap sebagai post_id
  5. 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:

  1. Method GET ke blogs/ akan difungsikan sebagai menampilkan data
  2. Method POST ke blogs/ akan difungsikan sebagai menambahkan data
  3. Method GET ke blogs/id akan difungsikan sebagai menampilkan data id bersangkutan
  4. Method PUT ke blogs/id akan difungsikan sebagai mengubah data id bersangkutan
  5. 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