İçeriğe geç

Django Generic Relations

Herkese merhabalar.  Daha önce Django’da ki Content Type frameworkünü görmüş fakat ne işe yaradığı hakkında bir fikir yürütememiş iseniz bu yazı ile bu fikri oluşturacağız.

Django includes a <span class="pre">contenttypes</span> application that can track all of the models installed in your Django-powered project, providing a high-level, generic interface for working with your models.

Dökümantasyonda da açıkca belirtildiği üzere bu framework Django projenizin uygulamalarında yaşayan modelleri takip etmekle görevlidir. Bunun yanında projeniz genelinde kullanılan tüm modeller ile dinamik ilişkiler kurabilmenize ve yönetebilmenize olanak sağlar.

Bu yazıda tam olarak bununla alakalı.

Kurulum.

Kurulum için eğer Django ile gelen yerleşik uygulamalara direkt olarak müdahele etmedi isek ekstra bir şey yapmamıza gerek kalmamakta. Fakat INSTALLED_APPS ‘ı konfigüre etmiş ve Content type’ı dışarı çıkartmış iseniz hızlıca tekrar içeri alabilirsiniz.

INSTALLED_APPS = [
    ...
    'django.contrib.contenttypes',
    ...
]

Örnek senaryo.

Örnek bir senaryo üzerinden Content Type ve Generic Relation’ları anlamaya çalışalım.

Bir e kitaplık uygulaması geliştirdiğimizi düşünelim. Bu uygulama 3 yeteneğe sahip olsun. Yazarlar, yazarlara ait olan kitaplar saklanabilsin. Bunun yanında kullanıcılarımız hem yazarlara hem kitaplara yorum yapabilsin.

Bu uygulamayı gerçekleştirme adına 3 adet modeli aşağıdaki gibi yazalım

from django.conf import settings
from django.db import models
class Author(models.Model):
    name = models.CharField(max_length=100)

    def __str__(self):
        return self.name


class Book(models.Model):
    author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name="books")
    name = models.CharField(max_length=100)
    page = models.PositiveSmallIntegerField()

    def __str__(self):
        return f"{self.author.name}'s book {self.name}"


class Comment(models.Model):
    author = models.ForeignKey(
        Author, on_delete=models.CASCADE, null=True, blank=True, related_name="comments"
    )
    book = models.ForeignKey(
        Book, on_delete=models.CASCADE, related_name="comments", blank=True, null=True
    )
    content = models.TextField()
    owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)

Basitçe üstünden geçelim. Yazarların ismini tutan bir Author modeli, Author modelin to-many bağlı ve kitapların bilgilerini tutan bir Book modeli.

Comment modeli ise biraz daha komplike. One-to many şeklinde bağlı olduğu birden fazla alan içermekte. Yorumu kimin oluşturudğu, yorum gövdesi ve can alıcı 2 noktasıyla bu yorumun bağlı olduğu Author veya Book nesnesi.

Yorumlarımızı potansiyel olarak hem Author hem Book modellerinden türeteceğimiz nesneler ile bağlantıya geçebilir.

Örnek kullanımlar:

comment_to_book = Comment.objects.create(owner=user,book=book,content="This is my favorite book")
comment_to_author = Comment.objects.create(owner=user,author=author,content="Favorite author!")

Yukarıdaki kullanım kesinlikle geçerli ve çalışan bir yaklaşım fakat bir düşünelim.

  • Ya kullanıcıların değerli yorumlarını saklayacağımız çok daha fazla bağlantılı nesne olsa idi?
  • Veritabanımız da bu kadar null değer tutmak pratitkte doğru bir yaklaşım mı?
  • Bu yaklaşım proje fazlası ile büyüdüğünde esnetilmeye müsat mi?

Düşüncelerinizi ve potansiyel çözüm yollarınızı duyuyor gibiyim. AuthorComment, BookComment olarak 2 model tutsam ve birbirinden tamami ile izole etsem?

Evet bu kesinlikle çalışacaktır. Yukarıda bahsi geçen bir kaç problemi çözecektir fakat kendimizi tekrar etmiş olmaz mıyız? Birbiri ile neredeyse aynı işi yapan, aynı alanları kapsayan bir sürü model?


İşte tam bu noktada Django Content Type ve Generic Relations’ lar devreye giriyor.

Aşağıdaki gibi bir kullanım ile modelimizi generic relationlar kurabilecek hale getirelim ve üstüne konuşalım.

from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType

class Comment(models.Model):
    content = models.TextField()
    owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveSmallIntegerField()
    content_object = GenericForeignKey("content_type", "object_id")

Sırası ile gidecek olursak content alanı yorumlarının gövdelerini tutma adına kullandığımız klasik bir Django Text Field. owner bu yorumu kullanıcı tablosundan bir kullanıcı ile one-to-many bağlamakta.

Diğer 3 alan ise Generic Relationlar ile alakalı.

content_type ile Content Type modelini bağlıyoruz. Böylece bu yorumun kayıtlı olan herhangi bir model ile ilişki kurabilmesini sağlıyoruz.

object_id alanı bu yorumun content_type alanında tanımlanmış olan modelin hangi nesnesi ile bağıntılı olacağını belirtmekte. Kısaca açıklayacak olursam admin kullanıcınızın id’si 1 ve bu yorum ona bağlanacak. Bu durumda content_type User modeliniz, object_id iniz ise 1 olacaktır.

content_object ise hemen üstündeki 2 tanımlama ile dinamik bir ilişki kurmak için. Diğer modeller ile ilişki kurduğunuzda üzerinde çalıştığınız ilişkiliyi nesneyi referans olarak göstermekte.

Not olarak GenericForeignKey alanının konfigüre edebilmesi adına hemen üstündeki 2 satır veriliyor, fakat bu alanların isimlendirilmeleri bu şekilde olmak zorunda değil. Django default olarak bu isimlendirmeleri tanıyor fakat siz kendiniz için daha anlaşılır isimlendirmeler kullanabilirsiniz.


Yorum yapalım!

comment = Comment.objects.create(owner=u,content="lorem",content_object=book)

ya da

content_type = ContentType.objects.get(app_label="example",model="book")
book = Book.objects.last()
new_comment = Comment.objects.create(object_id=book.id,content_type=content_type,owner=u,content="new comment")
new_comment
<Comment: Comment object (3)>

Bu kısımın anlaşılır olduğunu düşünüyorum fakat gelin biraz daha işleri kolaylaştıralım ve ilişki kurduğumuz model nesneleri üstünden yorumlarla bağlantıya geçelim.

class Author(models.Model):
    name = models.CharField(max_length=100)
    comments = GenericRelation('Comment')

class Book(models.Model):
    author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name="books")
    name = models.CharField(max_length=100)
    page = models.PositiveSmallIntegerField()
    comments = GenericRelation('Comment')

Modellerimize GenericRelation’ları ekliyoruz. Böylece artık direkt olarak nesnemiz üzerinden de bu süreci daha basit yönetebileceğiz.

Nasıl olacak? Django sihir yapacak 🙂

book.comments.create(owner=u,content="hey it's so simple!")
<Comment: Comment object (4)>

Generic Relation olarak verdiğimiz alan üzerinden direkt olarak işlemi gerçekleştirdik. Ne content_type ne object_id! Django bizim için her şeyi arka planda ayarladı ve uyguladı.

author = Author.objects.last()
author.comments.all()
<QuerySet [<Comment: Comment object (5)>, <Comment: Comment object (6)>]>
author.comments.all().count()
2

Kolayca ilişkili nesne üzerinden de sorgumuzu yaptığımıza göre tamam görünüyor!


Görüldüğü üzere Generic Relations’lar ile uygulama çok rahat bir şekilde genişletilebiliyor. Diyelim artık fotoğraflarında yorumlandığı bir e kütüphaneyiz.

Ana Comment modelini değiştirmeden tek yapmamız gereken yeni modelimizde gerekli ilişkiyi belirtmek. Gerisini Django bizim için yönetecek!

class Picture(models.Model):
    owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    picture = models.ImageField(upload_to="upload_images/")
    created = models.DateTimeField(auto_now_add=True)
    comments = GenericRelation('Comment')

Son sözler.

Generic Relations ilk aşamada biraz karmaşık görünmüş olabilir. Fakat ciddi anlamda uygulamanıza esneklik katacaktır.

Fakat projelerde uygulanacak her uygulama da kar/zarar maliyetini düşündüğümüz gibi bu yaklaşımı da uygularken düşünmemiz gerekmekte. Aksi halde klasik ilişkilendirmelere nazaran daha kompleks bir yapıyı sisteme dahil etmiş oluruz.

Karar sizin 🙂

Django Content Type framework’ü ile alakalı daha fazla okumak isterseniz link burada. Görüşmek üzere!

Tarih:Blog

Tek Yorum

  1. Anonim Anonim

    Teshekkurler! Achiklayici bir anlatim olmush.

Bir cevap yazın

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

Göster
Gizle