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! :)

Make a comment!

About Comments

Your email won't be published for any reason. It's only kept for archive reasons if i want to contact you. You can use markdown syntax.