How exactly do Django content types work?
I'm really having a difficult time grasping the concept of Django's content types. It feels very hackish and, ultimately, against how Python tends to do things. That being said, if I'm going to use Django then I have to work within the confines of the framework.
So I'm coming here wondering if anyone can give a practical real world example of how a content type works and how you would implement it. Almost all the tutorials (mostly on blogs) I have reviewed don't do a great job really covering the concept. They seem to pick up where the Django documentation left off (what seems like nowhere).
So you want to use the Content Types framework on your work?
Start by asking yourself this question: "Do any of these models need to be related in the same way to other models and/or will I be reusing these relationships in unforseen ways later down the road?" The reason why we ask this question is because this is what the Content Types framework does best: it creates generic relations between models. Blah blah, let's dive into some code and see what I mean.
# ourapp.models from django.conf import settings from django.db import models # Assign the User model in case it has been "swapped" User = settings.AUTH_USER_MODEL # Create your models here class Post(models.Model): author = models.ForeignKey(User) title = models.CharField(max_length=75) slug = models.SlugField(unique=True) body = models.TextField(blank=True) class Picture(models.Model): author = models.ForeignKey(User) image = models.ImageField() caption = models.TextField(blank=True) class Comment(models.Model): author = models.ForeignKey(User) body = models.TextField(blank=True) post = models.ForeignKey(Post) picture = models.ForeignKey(Picture)
Okay, so we do have a way to theoretically create this relationship. However, as a Python programmer, your superior intellect is telling you this sucks and you can do better. High five!
Enter the Content Types framework!
Well, now we're going to take a close look at our models and rework them to be more "reusable" and intuitive. Let's start by getting rid of the two foreign keys on our
Comment model and replace them with a
# ourapp.models from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType ... class Comment(models.Model): author = models.ForeignKey(User) body = models.TextField(blank=True) content_type = models.ForeignKey(ContentType) object_id = models.PositiveIntegerField() content_object = GenericForeignKey()
So, what happened? Well, we went in and added the necessary code to allow for a generic relation to other models. Notice how there is more than just a
GenericForeignKey, but also a
ContentType and a
PositiveIntegerField for the
object_id. These fields are for telling Django what type of object this is related to and what the id is for that object. In reality, this makes sense because Django will need both to lookup these related objects.
Well, that's not very Python-like... its kinda ugly!
You are probably looking for air-tight, spotless, intuitive code that would make Guido van Rossum proud. I get you. Let's look at the
GenericRelation field so we can put a pretty bow on this.
# ourapp.models from django.contrib.contenttypes.fields import GenericRelation ... class Post(models.Model): author = models.ForeignKey(User) title = models.CharField(max_length=75) slug = models.SlugField(unique=True) body = models.TextField(blank=True) comments = GenericRelation('Comment') class Picture(models.Model): author = models.ForeignKey(User) image = models.ImageField() caption = models.TextField(blank=True) comments = GenericRelation('Comment')
Bam! Just like that you can work with the Comments for these two models. In fact, let's go ahead and do that in our shell (type
python manage.py shell from your Django project directory).
>>> from django.contrib.auth import get_user_model >>> from ourapp.models import Picture # We use get_user_model() since we are referencing directly User = get_user_model() # Grab our own User object >>> me = User.objects.get(username='myusername') # Grab the first of our own pictures so we can comment on it >>> pic = Picture.objects.get(author=me) # Let's start making a comment for our own picture >>> pic.comments.create(author=me, body="Man, I'm cool!") # Let's go ahead and retrieve the comments for this picture now >>> pic.comments.all() [<Comment: "Man, I'm cool!">]
It's that simple.
What are the other practical implications of these "generic" relations?
Generic foreign keys allow for less intrusive relations between various applications. For example, let's say we pulled the Comment model out into it's own app named
chatterly. Now we want to create another application named
noise_nimbus where people store their music to share with others.
What if we want to add comments to those songs? Well, we can just draw a generic relation:
# noise_nimbus.models from django.conf import settings from django.contrib.contenttypes.fields import GenericRelation from django.db import models from chatterly.models import Comment # For a third time, we take the time to ensure custom Auth isn't overlooked User = settings.AUTH_USER_MODEL # Create your models here class Song(models.Model): ''' A song which can be commented on. ''' file = models.FileField() author = models.ForeignKey(User) title = models.CharField(max_length=75) slug = models.SlugField(unique=True) description = models.TextField(blank=True) comments = GenericRelation(Comment)
I hope you guys found this helpful as I would have loved to have come across something that showed me the more realistic application of
Is this too good to be true?
As with anything in life, there are pros and cons. Anytime you add more code and more abstraction, the underlying processes becomes heavier and a bit slower. Adding generic relations can add a little bit of a performance dampener despite the fact it will try and smart cache its results. All in all, it comes down to whether the cleanliness and simplicity outweighs the small performance gains. For me, the answer is a million times yes.
There is more to the Content Types framework than I have displayed here. There is a whole level of granularity and more verbose usage, but for the average individual, this is how you will be using it 9 out of 10 times in my opinion.
Generic relationizers(?) beware!
A rather large caveat is that when you use a
GenericRelation, if the model which has the
GenericRelation applied (
Picture) is deleted, all related (
Comment) objects will also be deleted. Or at least as of the time of this writing.
★ Back to homepage or read more recommendations: