threadedcomments has two basic models that are included and supported by default: ThreadedComment, and FreeThreadedComment. The difference between these two models is slight. In essence, every ThreadedComment instance is associated with a user from django.contrib.auth.models.User. While FreeThreadedComment has no such user association, it provides a few other identifying fields instead.
Below is a listing of all of the fields that are shared by both ThreadedComment and FreeThreadedComment.
These first three fields deal with the object with which this comment is associated:
content_type [ForeignKey]: | |
---|---|
required The content type (django.contrib.contenttypes.models.ContentType) instance with which this comment is associated. | |
object_id [PositiveIntegerField]: | |
required The value of the primary key to which this comment is associated. | |
content_object [GenericForeignKey]: | |
Combines the content_type and object_id fields to act as a virtual foreign key to the associated object. |
The next field is how we build our “threads”, or hierarchy:
parent [ForeignKey]: | |
---|---|
A self-referencing foreign key to whom this comment is addressed, being None if it is a toplevel comment (addressed directly to the original content object). |
The following fields deal with keeping track of dates of some events on this comment:
date_submitted [DateTimeField]: | |
---|---|
The date and time that this comment was saved for the first time. | |
date_modified [DateTimeField]: | |
The most recent date and time that this comment was saved. | |
date_approved [DateTimeField]: | |
The date and time that this comment set as is_approved=True. |
The following fields deal with the actual content of the comment itself:
comment [TextField]: | |
---|---|
required The content of the comment. | |
markup [IntegerField]: | |
required The format of the comment. Values may be MARKDOWN, TEXTILE, REST, PLAINTEXT. This can be used for display purposes when deciding how to format the comment for viewing. |
The following fields have to do with the status of the comment. Combined, they describe whether the comment should be viewable or not:
is_public [BooleanField]: | |
---|---|
required This field defaults to True, but will bet set to False if any of the moderation checks in an associated Moderator fail. | |
is_approved [BooleanField]: | |
required This field defaults to False, and it acts as an administrative override. It allows administrators to manually “whitelist” comments which fail a moderation test resulting in is_public being set to False. |
The following field is extra metadata that may be generally useful:
ip_address [IPAddressField]: | |
---|---|
This field stores the IP address of the computer that is submits the comment. |
ThreadedComment is useful for allowing registered users to comment. It is for this reason that there is a required foreign key to User.
user [ForeignKey]: | |
---|---|
Associates this comment with a particular user from django.contrib.auth.models.User. |
FreeThreadedComment is better suited for allowing just about anyone to post comments on an item. Instead of being associated with a particular user, instead it asks for some additional information like name and e-mail.
name [CharField]: | |
---|---|
required The name of the person leaving the comment. | |
website [URLField]: | |
If the person leaving the comment runs or is affiliated with a website, this is where that information would be stored. | |
email [EmailField]: | |
An e-mail address where the person leaving the comment can be reached. |
Since it’s not a feature of most databases to store hierarchical information, some methods are needed to be able to access that hierarchical information in a meaningful way. Also, since these comment objects can attach to any object, some convenience methods are helpful to query for and to create comments. These types of methods do not belong on the model itself, but rather on the Manager.
get_tree(content_object, root=None): | |
---|---|
Runs a depth-first search on all of the comments attached to the given content object. It then annotates a depth field which is an integer representing how many nodes away it is from the root node (the content object itself). It also orders the comments appropriately into threads. If a root comment is specified, the comment tree will start at that specific comment. This way, one could get a single thread of the comments. | |
create_for_object(content_object, **kwargs): | |
Wraps the create method, and automatically fills out content_type and object_id fields based on those given by the content object. | |
get_or_create_for_object(content_object, **kwargs): | |
Wraps the get_or_create method, and automatically fills out content_type and object_id fields based on those given by the content object. | |
get_for_object(content_object, **kwargs): | |
Wraps the get method, and automatically fills out content_type and object_id fields based on those given by the content object. | |
all_for_object(content_object, **kwargs): | |
Prepopulates a QuerySet with all comments related to the given content_object. |
This manager simply adds all of the aformentioned common methods onto the default manager.
It can be used by using the objects property on either ThreadedComment or FreeThreadedComment. An example would be:
>>> FreeThreadedComment.objects.all()
[<FreeThreadedComment: This is a test.>, <FreeThreadedComment: spam>]
This manager adds all of the aformentioned common methods onto the default manager, and then restricts all results to either be is_public = True or (by way of administrative override) is_approved = True.
It can be used by using the public property on either ThreadedComment or FreeThreadedComment. An example would be:
>>> FreeThreadedComment.public.all()
[<FreeThreadedComment: This is a test.>]
Moderation is how you are able to assign custom functionality to comments. Almost all moderation settings take place by registering moderator objects in lieu of Django settings. This means that you may choose different options for comments on different pages, etc.
threadedcomments uses a customized version of _django-comment-utils for its comment moderation. If you have used _django-comment-utils with the built-in django.contrib.comments application, you will find all of this very familiar.
Caution!
Prior to version 0.3, threadedcomments used a custom moderation system, however, due to a lack of robustness in the included implementation, django-comment-utils is now a required dependency. This is a backwards-incompatible change, so take care when upgrading to re-visit your old moderation code.
In threadedcomments.moderation there exists two items of note:
CommentModerator has many different properties, most of which are listed in the django-comment-utils documentation. Listed below are the extra properties that can be set by using the CommentModerator provided by threadedcomments.
max_comment_length: | |
---|---|
The maximum length of a comment, in characters. Defaults to settings.DEFAULT_MAX_COMMENT_LENGTH. If a comment goes beyond this length, the manager will set is_public = False before saving. |
|
allowed_markup: | A list of which markup types this manager will allow. If a different markup is submitted, the manager will set is_public = False before saving. This list can be comprised of zero or more of the following imported constants: from threadedcomments.models import MARKDOWN, TEXTILE, REST, PLAINTEXT
|
max_depth: | The maximum “depth” of a comment. That is, it will take no more than max_depth parent relationship traversals to hit a comment with no parent. Defaults to settings.DEFAULT_MAX_COMMENT_DEPTH. If a comment goes beyond this depth, the manager will set is_public = False before saving. |
To register a manager with a model, first the moderator must be imported:
from threadedcomments.moderation import moderator
Then, the moderator has register and unregister functions.
register(model, moderator): | |
---|---|
Registers a model class with a manager or with the aformentioned keyword arguments. | |
unregister(model): | |
Unregisters the manager that’s currently associated with the given model. |
The following example will assume that a “BlogPost” model exists:
class BlogPost(models.Model):
title = models.CharField(max_length=128)
body = models.TextField()
allows_comments = models.BooleanField(default = True)
Using CommentModerator:
from threadedcomments.moderation import moderator, CommentModerator
class BlogPostModerator(CommentModerator):
akismet = True
enable_field = 'allows_comments'
max_depth = 5
moderator.register(BlogPost, BlogPostModerator)
What this has done is ensure that if allows_comments is set to false, all comments will be deleted. Also, the maximum depth of the comment tree will be 5 comments deep. Finally, each comment will be spam-checked by Akismet.
While it’s possible to import the comment models and use them to query for the appropriate comments, sometimes it’s easier to do some of that manipulation in the template. There are several tags and filters which will help to begin using threadedcomments.
To use any of these tags or filters, first make sure to have ‘threadedcomments’ listed in your INSTALLED_APPS, and then include the following in your template:
{% load threadedcommentstags %}
After “threadedcommentstags” has been loaded, all of the following become available.
{% get_comment_url OBJECT [PARENT=None] %}: | |
---|---|
Gets the URL to post to for the given object and optional parent comment. | |
{% get_comment_url_json OBJECT [PARENT=None] %}: | |
Gets the URL to post to for the given object and optional parent comment, which will respond with JSON (useful for AJAX). | |
{% get_comment_url_xml OBJECT [PARENT=None] %}: | |
Gets the URL to post to for the given object and optional parent comment, which will respond with XML (useful for AJAX). | |
{% get_free_comment_url OBJECT [PARENT=None] %}: | |
Gets the URL to post to for the given object and optional parent free comment. | |
{% get_free_comment_url_json OBJECT [PARENT=None] %}: | |
Gets the URL to post to for the given object and optional parent free comment, which will respond with JSON (useful for AJAX). | |
{% get_free_comment_url_xml OBJECT [PARENT=None] %}: | |
Gets the URL to post to for the given object and optional parent free comment, which will respond with XML (useful for AJAX). | |
{% get_comment_count for OBJECT as CONTEXT_VAR %}: | |
Calls ThreadedComment.public.all_for_object(OBJECT).count() and places the result into the specified context variable. | |
{% get_free_comment_count for OBJECT as CONTEXT_VAR %}: | |
Calls FreeThreadedComment.public.all_for_object(OBJECT).count() and places the result into the specified context variable. | |
{% auto_transform_markup COMMENT [as CONTEXT_VAR] %}: | |
Inspects the given comment and its markup, and either outputs it using the correct markup processor, or if a context variable is specified, outputs it into the context for further use. | |
{% get_threaded_comment_tree for OBJECT [TREE_ROOT] as CONTEXT_VAR %}: | |
Calls ThreadedComment.public.get_tree(OBJECT) and places the result into the specified context variable. TREE_ROOT is an optional comment or comment_id, which would be the root of the tree. This way, querying for a single thread of comments is made easy. | |
{% get_free_threaded_comment_tree for OBJECT [TREE_ROOT] as CONTEXT_VAR %}: | |
Calls FreeThreadedComment.public.get_tree(OBJECT) and places the result into the specified context variable. TREE_ROOT is an optional comment or comment_id, which would be the root of the tree. This way, querying for a single thread of comments is made easy. | |
{% get_threaded_comment_form as CONTEXT_VAR %}: | |
Gets an unbound ThreadedCommentForm and assigns it to the specified context variable. | |
{% get_free_threaded_comment_form as CONTEXT_VAR %}: | |
Gets an unbound FreeThreadedCommentForm and assigns it to the specified context variable. | |
{% get_latest_comments NUM_TO_GET as CONTEXT_VAR %}: | |
Gets the NUM_TO_GET latest ThreadedComment instances, ordered by date_submitted and assigns it to the specified context variable. | |
{% get_latest_free_comments NUM_TO_GET as CONTEXT_VAR %}: | |
Gets the NUM_TO_GET latest FreeThreadedComment instances, ordered by date_submitted and assigns it to the specified context variable. | |
{% get_user_comments for USERNAME as CONTEXT_VAR %}: | |
Gets the comments submitted by USERNAME, and assigns it to the specified context variable. | |
{% get_user_comment_count for USERNAME as CONTEXT_VAR %}: | |
Gets the number of comments submitted by USERNAME, and assigns it to the specified context variable. |
oneline: | Gets rid of all newlines and spaces in-between tags. This is useful for calling when passing, for example, a form to javascript. You could use it like so: <script type="text/javascript"> var form_html_fragment = '{{ form.as_ul|oneline }}'; </script> Now you could use that form_html_fragment Javascript variable to dynamically add or remove comment forms to the page. |
---|
threadedcomments now has support built-in for Gravatar, the “globally recognized avatar” system provided by Automattic. To use this functionality, load the gravatar template tags in your template like so:
{% load gravatar %}
After the gravatar template library has been loaded, a new tag and filter become available.
Generates a gravatar image URL based on the given parameters.
Format is as follows (The square brackets indicate that those arguments are optional.):
{% get_gravatar_url for emailvar [rating "R" size 80 default img:blank as contextvar] %}
Rating, size, and default may be either literal values or template variables. The template tag will attempt to resolve variables first, and on resolution failure it will use the literal value.
If as is not specified, the URL will be output to the template in place.
For all other arguments that are not specified, the appropriate default settings attribute will be used instead. See the Gravatar portion of the Settings section to see what those defaults are.
Generates a gravatar image URL based on the email address string provided and settings or defaults.
The best way to describe this filter is to show an example:
<img src="{{ user.email|gravatar }}" alt="Gravatar for {{ user.get_full_name }}" />
In this example, we have a standard Django user object. We’re using the e-mail address for that user to generate a gravatar image and the other attributes from that user to generate some alternate text for accessibility.
The maximum rating, default image, and image size of this gravatar are a result of the gravatar-specific settings provided. See the Gravatar portion of the Settings section to see what those settings are.
There are three basic view types included with threadedcomments: create, edit, and delete. The following are the URL names and what inputs they take.
tc_comment: |
|
||||||||
---|---|---|---|---|---|---|---|---|---|
tc_comment_parent: | |||||||||
|
|||||||||
tc_comment_ajax: | |||||||||
|
|||||||||
tc_comment_parent_ajax: | |||||||||
|
|||||||||
tc_free_comment: | |||||||||
The same as tc_comment, only it deals with FreeThreadedComment model objects instead of ThreadedComment objects. |
|||||||||
tc_free_comment_parent: | |||||||||
The same as tc_comment_parent, only it deals with FreeThreadedComment model objects instead of ThreadedComment objects. |
|||||||||
tc_free_comment_ajax: | |||||||||
The same as tc_comment_ajax, only it deals with FreeThreadedComment model objects instead of ThreadedComment objects. |
|||||||||
tc_free_comment_parent_ajax: | |||||||||
The same as tc_comment_parent_ajax, only it deals with FreeThreadedComment model objects instead of ThreadedComment objects. |
tc_comment_edit: | |||||
---|---|---|---|---|---|
|
|||||
tc_comment_edit_ajax: | |||||
|
|||||
tc_free_comment_edit: | |||||
The same as tc_comment_edit, only it deals with FreeThreadedComment model objects instead of ThreadedComment objects. |
|||||
tc_free_comment_edit_ajax: | |||||
The same as tc_comment_edit_ajax, only it deals with FreeThreadedComment model objects instead of ThreadedComment objects. |
tc_comment_delete: | |||
---|---|---|---|
|
|||
tc_free_comment_delete: | |||
|
If the key ‘preview’ is inserted into any of the following views, a preview page (‘threadedcomments/preview_comment.html’) will be shown:
The context will be:
next: | The page to which the user should be redirected after successfully posting a comment. This is usually to be used in a hidden input field in a form that tells the view where to go next. |
---|---|
form: | Either a FreeThreadedCommentForm or a ThreadedCommentForm, depending on the type of view. |
comment: | If the parameters in POST validate correctly with the appropriate form, the unsaved comment will be passed in through this variable. |
If a GET request is sent to the Delete views, a confirmation/preview page (‘threadedcomments/confirm_delete.html’) will be shown with context:
comment: | The comment object to be deleted. |
---|---|
is_free_threaded_comment: | |
True if comment to be deleted is a FreeThreadedComment, and False otherwise. | |
is_threaded_comment: | |
True if comment to be deleted is a ThreadedComment, and False otherwise. | |
next: | The page to which the user should be redirected after successfully deleting a comment. This is usually to be used in a hidden input field in a form that tells the view where to go next. |
After a comment is posted, choosing where to go next happens in this order:
threadedcomments provides a very basic AJAX implementation. Upon POSTing to the view, it will return a JSON or an XML (as requested by URL) representation of the object just committed. It’s up to you to dynamically insert its content into the comment thread.
If a view encounters an error (form validation being the most common case), then depending on whether the comment is being created using the JSON, XML, or plain HTML APIs, different things will occur. If it’s using the JSON API, then it will return a JSON-formatted error string. If it’s using XML, it will return an XML-formatted error string. Otherwise, it will redirect the user to a preview page where he/she can edit the comment until it no longer has any errors. At this point, the user will be redirected to the page to which they would have originally been redirected.
Tip
When using FreeThreadedComment and after a comment is successfully saved, the successful name, website, and email fields will be placed into the session under the name successful_data. To use this data for the next time a user visits the website, simply populate the initial data for a form with the successful_data dict.
For example:
successful_data = request.session.get('successful_data', {})
form = FreeThreadedCommentForm(initial = successful_data)
Now that form will have the user’s simple data prepopulated. It’s that easy!
threadedcomments provides two basic forms that you can use and/or subclass to suit your needs: ThreadedCommentForm, and FreeThreadedCommentForm.
Simply includes two fields: comment, and markup.
comment: | required The content of the comment. |
---|---|
markup: | required The type of markup that the comment is using. This may be easily overridden in a subclass to become a HiddenInput, if the user is not to be given a choice of markup options. |
Includes a few fields: comment, name, website, email, and markup.
comment: | required The content of the comment. |
---|---|
name: | required The name of the person |
website: | A website for the commenter, if he or she has one to list. |
email: | An e-mail address where the person leaving the comment can be reached. |
markup: | required The type of markup that the comment is using. This may be esaily overridden in a subclass to become a HiddenInput, if the user is not to be given a choice of markup options. |
Most of the configuration will be done via the use of the Moderation features of threadedcomments itself, but there are a few settings that you might want or need to apply globally in the settings file.
AKISMET_API_KEY: | |
---|---|
Your API key for Akismet. This will only be needed if you enable Akismet moderation of comments on one or more of your models. | |
DEFAULT_MAX_COMMENT_LENGTH = 1000: | |
The default max_length that the comment forms should validate against if an override isn’t specified in per-model moderation. If this isn’t specified, then a default DEFAULT_MAX_COMMENT_LENGTH of 1000 is used. | |
DEFAULT_MAX_COMMENT_DEPTH = 8: | |
The default max_depth that the moderation should validate against if it isn’t explicitly defined in the moderator. Keep in mind that if no moderator is registered against a model, this default depth will not be adhered to either. | |
DEFAULT_MARKUP = PLAINTEXT: | |
Every comment which is added to any object has not only some text, but that text may be stored in some markup language like restructured text or textile. If this information is not provided to the form, then the comment will fall back to a default of whatever is specified here. The default is PLAINTEXT. |
GRAVATAR_MAX_RATING = "R": | |
---|---|
The default maximum rating to display for gravatars. Can be “G”, “PG”, “R”, or “X”. |
GRAVATAR_SIZE = 80: | |
---|---|
The size in which the gravatar should be displayed. Values may be from 1 to 80, inclusive. |
This is as good as it gets for now. Until more documentation is posted, check out Tutorial 1. Beyond that, there’s fairly extensive in-line documentation, so you can browse the source code. If you’re the kind of person who likes to scan doctests, you can check the tests out here.