İçeriğe geç

Django Template İçerisinde Vue.js Kullanmak

Django ile MVT mimarisine sahip harika bir geleneksel web uygulaması geliştirdiğimizi düşünelim. Bir noktada ise template’ler üzerinde etkileşimi arttırmamız gerekti.

Bu durumda ne yapabiliriz?

Backendi tamamen DRF ile baştan kurguladıktan sonra, frontend tarafında ayrık ve karmaşık bir SPA (Single Page Application) geliştirebilir ya da jQuery gibi biraz modası geçmiş bir teknolojiyi templateler içerisinde kullanabiliriz.

Bugün Django template içerisinde, Vue.js kullanarak yukarıdaki 2 çözüm yoluna bir alternatif oluşturacağız.

Gün sonunda basit bir to-do uygulaması oluşturacak, Vue.js ile veriyi ve templatei dinamik olarak yönetebilecek ve backend ile iletişim kuruyor olacağız.

Gün sonuna kadar neler yapacağız 😄?

  • Vue.js i projeye dahil edecek, template içerisinde bir Vue uygulaması oluşturacağız.
  • Django viewdan dönen contextleri, Vue uygulamasında kullanılabilir bir veri haline getirip render edeceğiz.
  • Axios ile bu apiyi consume edecek, veriyi Vue ile dinamik olarak template üzerinde yöneteceğiz.

Spoiler.

Oluşturacağımız uygulamanın son hali;

demo app gifUygulama reposu.


İlk adımlar ve Vue.js’in dahil edilmesi.

Öncelikle basit bir contexte sahip, todo uygulamasının template’ini render eden bir view oluşturalım.

# todos/views.py

class VueTodoTemplateView(TemplateView):
    template_name = "todos/index.html"

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context["first_message"] = "Hello Vue.js in Django Template!"
        context["even_numbers"] = [x for x in range(11) if x % 2 == 0]
        return context

Basit bir mesaj ve 10 a kadar olan çift sayıları context içerisine bağladık.

Template tarafında Vue uygulamasını oluşturalım.

Öncelikle CDN üzerinde Vue’yi dahil edelim.

https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js

Vue uygulamasını oluşturalım.

let app = new Vue({
    el: '#app',
    delimiters: ["[[", "]]"],
    data() {
        return {
            vueMessage: "Vue app."
        }
    },
    created() {
        console.log('Wow vue app is created!');
    }
})

Sırasıyla, Vue instance’ının kontrol altında tutacağı container elementin id’sini, delimiter ve basit bir data tanımladık.

Vue varsayılan olarak {{someVariable}} ile veri gösterimi yapar. Django template üzerindede aynı şekilde bu işlem gerçekleştiğinden delimiter değişimi yapıldı.

Template’in son hali;

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js

    <title>Vue Template View</title>
</head>

<body>

    <div id="app">
        [[vueMessage]]
    </div>

</body>

<script>

    let app = new Vue({
        el: '#app',
        delimiters: ["[[", "]]"],
        data() {
            return {
                vueMessage: "Vue app."
            }
        },
        created() {
            console.log('Wow vue app is created!');
        }
    })
</script>

</html>

Projenizi ayağa kaldırıp ilgili endpointe gittiğinzde “Vue app.” mesajını görüyorsanız; tebrikler Django template içerisinde bir Vue uygulaması ayağa kaldırdınız!

Django context ve Vue uygulaması data ilişkisi.

Şu haliyle Django view tarafından döndürülen context verisini kullanmıyoruz. Ama kullanacağız 😅

Bu contexti ilk olarak JavaScript tarafından kullanılabilir hale getirmeliyiz.

Bu dönüştürme sırasında XSS ataklarını da önlememiz gerekmekte. Bu sebeple Django template language içerisinde varsayılan olarak tanımlı olan json_script tagini kullanacağız.

json_script tagi native Python tipten JSON tipe dönüşüm gerçekleştirecek.

Django Bu güvenli dönüşümü yaparken HTML içerisinde bir script tagi oluşturacak ve belirlediğimiz id ile işaretleyecek ki daha sonrasında içerisinden veriyi alabilelim.

{{ first_message | json_script:'first_message' }}
{{ even_numbers | json_script:'even_numbers' }}

Dönüşümü sağladıktan sonra, dönüştürülen değeri almak için script tagi içerisinde;

const firstMessage = JSON.parse(document.getElementById('first_message').textContent);

işlemini yaptık.

Veriyi nasıl dönüştüreceğimizi artık biliyoruz.

Django viewından dönen contexti Vue uygulamasında kullanılabilir hale getirelim.

<script>
    const firstMessage = JSON.parse(document.getElementById('first_message').textContent);
    const evenNumbers = JSON.parse(document.getElementById('even_numbers').textContent);

    let app = new Vue({
        el: '#app',
        delimiters: ["[[", "]]"],
        data() {
            return {
                vueMessage: "Vue app.",
                firstMessage: firstMessage,
                evenNumbers: evenNumbers
            }
        },
        created() {
            console.log(this.evenNumbers)
            console.log(this.firstMessage)
        }
    })
</script>

Tarayıcı konsoluna gittiğinizde verinin dönüştürüldüğünü ve Vue uygulaması  tarafından kullanılabilir olduğunu görebiliriz. Ekrana basalım!

<body>

    {{ first_message | json_script:'first_message' }}
    {{ even_numbers | json_script:'even_numbers' }}

    <div id="app">
        [[vueMessage]]
        <br>
        [[firstMessage]]
        <ul>
            <li v-for="number in evenNumbers" :key="number">
                [[number]]
            </li>
        </ul>
    </div>

</body>

Sayıların ekranda Vue tarafından görüntülendiğini görebiliriz. Bu bölümü kapatmadan önce, son olarak sayıları karıştırmak için bir buton ve metod ekleyelim.

data keyi altına, Vue için bir method tanımlayalım. (Kabul ediyorum kötü bir sıralama algoritması 😅)

methods: {
            shuffle() {
                this.evenNumbers.sort(() => .5 - Math.random());
            }
        }

Bu metodu her tık aldığında çağıracak olan butonuda ekleyelim.

<button @click="shuffle()">
          Shuffle me!
      </button>

Kaydedip tarayıcıya gidelim. Keyifli değil mi 😂

Artık Django template içerisinde Vue uygulaması ayağa kaldırmayı, context verilerini kullanılabilir hale getirmeyi biliyoruz. Biraz daha işe yarar işlemler yapabiliriz.


API Consume etmek.

Todo uygulamamızı geliştirmeye başlayabiliriz.

İlk olarak model;

class Todo(models.Model):
    title = models.CharField(max_length=150)
    is_done = models.BooleanField(default=False)

Daha sonrasında gerekli serializer ve viewseti oluşturalım.

#todos/serializers.py

from rest_framework import serializers
from todos.models import Todo

class TodoSerializer(serializers.ModelSerializer):
    class Meta:
        model = Todo
        fields = "__all__"
#todos/views.py

from rest_framework import viewsets
from todos.models import Todo

class TodoViewset(viewsets.ModelViewSet):

    queryset = Todo.objects.all().order_by("id")
    serializer_class = TodoSerializer

Todolarımız için RESTFUL bir endpoint API endpointi oluşturduk.

☢️ Burada aklınıza neden doğrudan templatei render eden Django view üzerinde bu işlemleri yapmadık diye bir soru gelmiş olabilir. Pekala yapabilirdik fakat daha fazla efor harcayarak gönderiyi uzatmamak ve odağı kaybetmemek adına DRF tercih edildi. Bu efor, qs ve model objelerinin serialize edilmesi, ilgili GET, POST, PUT, PATCH vs endpointlerinin yazılması vs gibi konuları kapsıyor.

 

Daha sonra,  uygulamanın biraz daha güzel gözükmesi için Boostrap’i ve HTTP isteklerini backende çıkabilmek için Axios’ı CDN üzerinden dahil edelim.

<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js

Vue uygulamasını oluşturalım ve sayfaya Vue instancenı sayfaya mount edilidğinde todoları API isteği çıkarak alalım.

<script>
    const url = window.location.hostname

    let app = new Vue({
        el: "#app",
        data() {
            return {
                todos: [],
                newTodo: ""
            }
        },
        delimiters: ['[[', ']]'],

        mounted() {
            axios.get("/api/todos/", {
                headers: {
                    "Accept": "application/json"
                }
            })
            .then((res) => {
                this.todos = res.data
            })
            .catch((err) => {
                    console.log(err);
            })
        }

    })
</script>

Uygulama gövdesini yani todoları listeleyecek yapıyı, yeni bir todo eklemek için gerekli input alanını ve todoyu tamamlanmış ya da tamamlanmamış olarak işaretlemek için gerekli olan butonları kurgulayalım.

<body>
    <div class="container mx-auto mt-5">
        <div class="row">
            <div class="col-12">
                <div id="app">
                    <ul v-if="todos.length > 0" class="list-group">

                        <li :style="lineType(todo.is_done)" v-for="todo in todos" :key="todo.id"
                            class="list-group-item"> <span>[[todo.title]]</span>

                            <button @click="completeTodo(todo.id)" class="btn btn-info float-right">[[todo.is_done ?
                                "Uncomplete" : "Complete"]].</button>

                        </li>
                    </ul>
                    <ul v-else class="list-group">
                        <li class="list-group-item">
                            No todo good to go!
                        </li>
                    </ul>

                    <div class="input-group mb-3 mt-5">
                        <input v-model="newTodo" type="text" class="form-control" placeholder="Any todo?"
                            aria-describedby="button-addon2">
                        <div class="input-group-append">
                            <button :disabled="!newTodo.length > 0" @click="createNewTodo()" class="btn btn-primary"
                                type="button">Add.</button>

                        </div>

                    </div>
                </div>
            </div>
        </div>
    </div>

</body>

Yazının başındaki gife benzer bir görüntü elde etmiş olmanız gerekmekte.

Yeni bir todo ekleme, güncelleme için kullanılacak metodlar ve tamamlanmış todolar için dinamik style binding yapacak metodları da Vue uygulamasını ekleyelim.

methods: {
    createNewTodo() {

        let payload = {
            "title": this.newTodo
        }
        axios.post(
            "/api/todos/",
            payload
        )
            .then((res) => {
                this.todos.push(res.data)
                this.newTodo = ""
            })
            .catch((err) => {
                console.log(err);
            })
    },
    completeTodo(todoPk) {
        let objIndex = this.todos.findIndex((obj => obj.id == parseInt(todoPk, 10)));
        let obj = this.todos[objIndex]
        let payload = { is_done: !obj.is_done }
        let updateUrl = `/api/todos/${todoPk}/`
        axios.patch(updateUrl, payload)
            .then((res) => {
                this.todos[objIndex].is_done = res.data.is_done
            })
            .catch((err) => {
                console.log(err);
            })
    },
    lineType(isDone) {
        if (isDone) {
            return {
                textDecoration: "line-through"
            }
        }
    }
},

Güncelleme metoduna göz atacak olursak;

completeTodo(todoPk) {
               let objIndex = this.todos.findIndex((obj => obj.id == parseInt(todoPk, 10)));
               let obj = this.todos[objIndex]
               let payload = { is_done: !obj.is_done }
               let updateUrl = `/api/todos/${todoPk}/`
               axios.patch(updateUrl, payload)
                   .then((res) => {
                       this.todos[objIndex].is_done = res.data.is_done
                   })
                   .catch((err) => {
                       console.log(err);
                   })
           },

Butona tıklandığında, içerisine geçilen pk ile ilk olarak obj Vue uygulaması içerisindeki todos verisinde aranıyor. Payload, url oluşturuluyor ve patch isteği ilgili API endpointe çıkılıyor. İstek başarılı bir şekilde dönmüşse ilgili todo güncelleniyor ya da hata ekrana basılıyor.

Tüm bu döngü tamamlandığında, Django güncellenmiş bir template render etmiyor, sayfanın kendisine atılan bir POST isteğini de handle etmiyor ya da veriyi ön tarafta değiştirmiyor. Tüm olay ve dinamizm Vue kontrolünde sağlanıyor.

Biz de istediğimizi elde etmiş oluyoruz!


Son sözler.

Görüldüğü üzere, Vue.js çok kolay bir şekilde halihazırda olan uygulamanızın belirli kısımlarına entegre edilebilir ve dinamizm kazandırabilir.

Frontend tarafında fazla geliştirme yapan bir yazılımcı değilim ama zorundaysam Vue.js benim her zaman birinci tercihim oluyor.

Bu yazı bir Vue tutorialı olma amacı taşımadığından bazı kısımlar boğucu olmuş ya da anlaşılması güç olmuş olabilir. O yüzden Vue ile tanışabilmeniz için aşağıya bir kaç kaynak bırakacağım.

Uygulamanın repo linkini yine gifin hemen altında eklenmiş olarak bulabilirsiniz.

Görüşmek üzere!


Vue Introduction

Vue Template Syntax

Vue Event Handling

 

Tarih:Blog

İlk Yorumu Siz Yapın

Bir cevap yazın

E-posta hesabınız yayımlanmayacak.

Göster
Gizle