Playing with signals and threadedcomments
In my blog I use Eric's threadedcomments application (although in a non-threaded way!).
I noticed yesterday that when you delete a post, the related comments remain in the database and that was a good opportunity to use django signals for the first time!
Planning
We 'll introduce two new settings:
DELETECOMMENTSON_DELETE: Boolean, Always delete comments when the related objects are deleted.
DELETECOMMENTSONDELETEMODELS: List, Delete comments when the related model is in list. The list should be in the form ('myapp.mymodel',)
A small test
Before writing the actual code, let's write a simple test:
#######################################
### delete_comments_on_delete Tests ###
#######################################
>>> import datetime
>>> from threadedcomments.models import FreeThreadedComment, TestModel
>>> from django.contrib.contenttypes.models import ContentType
>>> topic = TestModel.objects.create(name = "Test")
>>> topic.save()
>>> comment = FreeThreadedComment.objects.create_for_object(
... topic, name = "Eric", ip_address = '127.0.0.1',
... comment = 'This is fun! This is very fun!',
... )
>>> comment_id = comment.id
>>> topic.delete()
>>> FreeThreadedComment.objects.filter(id=comment_id).count()
0
We run the test now and it fails, so we have something to fix!
Coding
We now have to write a handler function that connects to the post_save signal for the models. After an object instance is deleted the handler is called and deletes the related comments from the database.
# handlers.py
# Loads the installed apps
from django.db.models.loading import get_models
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.dispatch import dispatcher
from django.db.models import signals
from models import FreeThreadedComment, ThreadedComment
DELETE_COMMENTS_ON_DELETE = getattr(settings, 'DELETE_COMMENTS_ON_DELETE', False)
DELETE_COMMENTS_ON_DELETE_MODELS = getattr(settings, 'DELETE_COMMENTS_ON_DELETE_MODELS', [])
def hook_delete_comments(sender, instance, signal, *args, **kwargs):
FreeThreadedComment.objects.all_for_object(instance).delete()
ThreadedComment.objects.all_for_object(instance).delete()
def delete_comments_on_delete(*args):
for model in args:
dispatcher.connect(hook_delete_comments, signal=signals.post_delete, sender=model)
models = []
if DELETE_COMMENTS_ON_DELETE:
models = get_models()
delete_comments_on_delete(*models)
elif DELETE_COMMENTS_ON_DELETE_MODELS:
for pair in DELETE_COMMENTS_ON_DELETE_MODELS:
app_name, model_name = pair.split('.')
type = ContentType.objects.get(app_label=app_name, model=model_name)
models += [type.model_class()]
delete_comments_on_delete(*models)
Depending on our settings delete_comments_on_delete iterates through the models and registers the post_delete handler.
We want to make sure that is code is executed when threadedcomments is loaded so we import it from __init__.py
#__init__.py
import handlers
Conclusion
The good thing with this solution is that it doesn't touch another application's code (rewriting the delete method for ex.).This can be useful for apps based on generic relations so they can interact with the models in a transparent way.
Comments
Eric Florenzano
0 minutes since post.
Very nice and clean-looking blog. You're in my RSS reader now. I've responded to your issue on the django-threadedcomments tracker.
Christos Trochalakis
0 minutes since post.
Thank you Eric, I'm glad you like it.
I just started blogging so let's see how it goes. I hope my english will get better over time! :)