I am using Django 2.0.8 and Python 3.5 for a project. I have different models in my project, some of which, I want to allow commenting on - with both the object (e.g. a blogpost) and comments to the blogpost being likeable.
I am using the threaded comments django app to provide commenting functionality.
Assuming I have a model Foo (see below):
from django.db import models
from django.conf import settings
class Foo(models.Model):
owner = models.ForeignKey(settings.AUTH_USER_MODEL, blank=False, null=False, default=1, on_delete = models.PROTECT)
# ...
class Likeable():
pass
class Rateable():
pass
How could I use mixins (or any other mechanism for that matter), to make the object Foo "commentable" (i.e. an object which can be commented upon), "likeable" (i.e. an object which can be commented upon) and "rateable" (i.e. an object which can be rated?)- bearing in mind that comments on an objects may be BOTH liked and rated.
According to django documentation , you can achieve this using the Content types Framework. ContentType
is a generic model that permits you to track all the models included in INSTALLED_APPS
using for that their app_label
, model_name
and pk
. The way it works is easy:
Comment
modelfrom django.db import models
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.conf import settings
class Comment(models.Model):
# Generic relation fields
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
# Model specific fields
author = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE
)
comment = models.TextField()
created = models.DatetimeField(auto_now_add=True)
# ...
from django.db import models
from django.contrib.contenttypes.fields import GenericRelation
class Commentable(models.Model):
comments = GenericRelation(Comment)
class Meta:
abstract = True
Commentable
model:from django.db import models
class Foo(Commentable, Likeable, ...):
# your stuff
# Add a new comment to Foo
foo = new Foo()
foo.save()
foo.comments.create(author=author, comment="Your comment")
# Retrieve all comments from an specific user no matter the base model
comments = Comment.objects.filter(author=author)
EDIT As @ozren1983 said, each approach has its own downsides, but this is the standard way to do it.
The main advantages are:
The main disadvantage is the lack of integrity checks of this generic relationship in database. But if you plan to use the django ORM strictly, nothing should be broken.
BONUS: Another approach that many projects use is inheriting the models (one to one relationship) from an specific one called Item
or Thread
. Then, you can add all the comments, likes, etc functionalities to this model. This is called multi-table inheritance. An example:
from django.db import models
class Thread(models.Model):
pass
class Comment(models.Model):
# Relation with thread
thread = models.ForeignKey(
Thread,
on_delete=models.CASCADE,
related_name="comments"
)
# Model specific fields
author = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE
)
comment = models.TextField()
created = models.DatetimeField(auto_now_add=True)
# ...
class Foo(Thread):
pass
Unlike using the generic relationships, the main advantage of this method is that, this way, you have database integrity checks.
The main disadvantage is that your database structure could become complex.
Based on my experience and recommendations in Two scoops of Django, I would advise against using GenericForeignKey
and GenericRelation
. Two big downsides of that approach are:
Instead, I would use following approach. Let's say you have 3 models:
class User(models.Model):
username = models.CharField(max_length=255)
class Author(models.Model):
name = models.CharField(max_length=255)
class Post(models.Model):
title = models.CharField(max_length=255)
author = models.ForeignKey(Author)
Add abstract Like
model, and use it as base class for other models that will implement liking functionality.
class Like(models.Model):
user = models.ForeignKey(User)
date_created = models.DateTimeField(auto_now_add=True)
class Meta:
abstract = True
class AuthorLike(Like):
author = models.ForeignKey(Author)
class PostLike(Like):
post = models.ForeignKey(Post)
Similarly, add abstract Rating
model and use it as a base class:
class Rating(models.Model):
user = models.ForeignKey(User)
rate = models.PositiveSmallIntegerField()
date_created = models.DateTimeField(auto_now_add=True)
class Meta:
abstract = True
class AuthorRating(Rating):
author = models.ForeignKey(Author)
class PostRating(Rating):
post = models.ForeignKey(Post)
You can use same approach to enable liking and rating to the Comments model you are using:
from threadedcomments.models import ThreadedComment
class ThreadedCommentRating(Rating):
threadedcomment = models.ForeignKey(ThreadedComment)
class ThreadedCommentLike(Like):
threadedcomment = models.ForeignKey(ThreadedComment)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With