summaryrefslogtreecommitdiff
path: root/tests/comment_tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests/comment_tests')
-rw-r--r--tests/comment_tests/__init__.py0
-rw-r--r--tests/comment_tests/custom_comments/__init__.py32
-rw-r--r--tests/comment_tests/custom_comments/forms.py5
-rw-r--r--tests/comment_tests/custom_comments/models.py5
-rw-r--r--tests/comment_tests/custom_comments/views.py14
-rw-r--r--tests/comment_tests/fixtures/comment_tests.json53
-rw-r--r--tests/comment_tests/fixtures/comment_utils.xml15
-rw-r--r--tests/comment_tests/models.py39
-rw-r--r--tests/comment_tests/tests/__init__.py95
-rw-r--r--tests/comment_tests/tests/app_api_tests.py80
-rw-r--r--tests/comment_tests/tests/comment_form_tests.py85
-rw-r--r--tests/comment_tests/tests/comment_utils_moderators_tests.py97
-rw-r--r--tests/comment_tests/tests/comment_view_tests.py324
-rw-r--r--tests/comment_tests/tests/feed_tests.py53
-rw-r--r--tests/comment_tests/tests/model_tests.py58
-rw-r--r--tests/comment_tests/tests/moderation_view_tests.py315
-rw-r--r--tests/comment_tests/tests/templatetag_tests.py171
-rw-r--r--tests/comment_tests/urls.py23
-rw-r--r--tests/comment_tests/urls_admin.py19
-rw-r--r--tests/comment_tests/urls_default.py9
20 files changed, 1492 insertions, 0 deletions
diff --git a/tests/comment_tests/__init__.py b/tests/comment_tests/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/comment_tests/__init__.py
diff --git a/tests/comment_tests/custom_comments/__init__.py b/tests/comment_tests/custom_comments/__init__.py
new file mode 100644
index 0000000000..598927eace
--- /dev/null
+++ b/tests/comment_tests/custom_comments/__init__.py
@@ -0,0 +1,32 @@
+from django.core import urlresolvers
+from regressiontests.comment_tests.custom_comments.models import CustomComment
+from regressiontests.comment_tests.custom_comments.forms import CustomCommentForm
+
+def get_model():
+ return CustomComment
+
+def get_form():
+ return CustomCommentForm
+
+def get_form_target():
+ return urlresolvers.reverse(
+ "regressiontests.comment_tests.custom_comments.views.custom_submit_comment"
+ )
+
+def get_flag_url(c):
+ return urlresolvers.reverse(
+ "regressiontests.comment_tests.custom_comments.views.custom_flag_comment",
+ args=(c.id,)
+ )
+
+def get_delete_url(c):
+ return urlresolvers.reverse(
+ "regressiontests.comment_tests.custom_comments.views.custom_delete_comment",
+ args=(c.id,)
+ )
+
+def get_approve_url(c):
+ return urlresolvers.reverse(
+ "regressiontests.comment_tests.custom_comments.views.custom_approve_comment",
+ args=(c.id,)
+ )
diff --git a/tests/comment_tests/custom_comments/forms.py b/tests/comment_tests/custom_comments/forms.py
new file mode 100644
index 0000000000..07918ddb8c
--- /dev/null
+++ b/tests/comment_tests/custom_comments/forms.py
@@ -0,0 +1,5 @@
+from django import forms
+
+
+class CustomCommentForm(forms.Form):
+ pass
diff --git a/tests/comment_tests/custom_comments/models.py b/tests/comment_tests/custom_comments/models.py
new file mode 100644
index 0000000000..646f6255f2
--- /dev/null
+++ b/tests/comment_tests/custom_comments/models.py
@@ -0,0 +1,5 @@
+from django.db import models
+
+
+class CustomComment(models.Model):
+ pass
diff --git a/tests/comment_tests/custom_comments/views.py b/tests/comment_tests/custom_comments/views.py
new file mode 100644
index 0000000000..1c3a974367
--- /dev/null
+++ b/tests/comment_tests/custom_comments/views.py
@@ -0,0 +1,14 @@
+from django.http import HttpResponse
+
+
+def custom_submit_comment(request):
+ return HttpResponse("Hello from the custom submit comment view.")
+
+def custom_flag_comment(request, comment_id):
+ return HttpResponse("Hello from the custom flag view.")
+
+def custom_delete_comment(request, comment_id):
+ return HttpResponse("Hello from the custom delete view.")
+
+def custom_approve_comment(request, comment_id):
+ return HttpResponse("Hello from the custom approve view.")
diff --git a/tests/comment_tests/fixtures/comment_tests.json b/tests/comment_tests/fixtures/comment_tests.json
new file mode 100644
index 0000000000..55e2161a4c
--- /dev/null
+++ b/tests/comment_tests/fixtures/comment_tests.json
@@ -0,0 +1,53 @@
+[
+ {
+ "model" : "comment_tests.book",
+ "pk" : 1,
+ "fields" : {
+ "dewey_decimal" : "12.34"
+ }
+ },
+ {
+ "model" : "comment_tests.author",
+ "pk" : 1,
+ "fields" : {
+ "first_name" : "John",
+ "last_name" : "Smith"
+ }
+ },
+ {
+ "model" : "comment_tests.author",
+ "pk" : 2,
+ "fields" : {
+ "first_name" : "Peter",
+ "last_name" : "Jones"
+ }
+ },
+ {
+ "model" : "comment_tests.article",
+ "pk" : 1,
+ "fields" : {
+ "author" : 1,
+ "headline" : "Man Bites Dog"
+ }
+ },
+ {
+ "model" : "comment_tests.article",
+ "pk" : 2,
+ "fields" : {
+ "author" : 2,
+ "headline" : "Dog Bites Man"
+ }
+ },
+
+ {
+ "model" : "auth.user",
+ "pk" : 100,
+ "fields" : {
+ "username" : "normaluser",
+ "password" : "34ea4aaaf24efcbb4b30d27302f8657f",
+ "first_name": "Joe",
+ "last_name": "Normal",
+ "email": "joe.normal@example.com"
+ }
+ }
+]
diff --git a/tests/comment_tests/fixtures/comment_utils.xml b/tests/comment_tests/fixtures/comment_utils.xml
new file mode 100644
index 0000000000..a39bbf63e1
--- /dev/null
+++ b/tests/comment_tests/fixtures/comment_utils.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<django-objects version="1.0">
+ <object pk="1" model="comment_tests.entry">
+ <field type="CharField" name="title">ABC</field>
+ <field type="TextField" name="body">This is the body</field>
+ <field type="DateField" name="pub_date">2008-01-01</field>
+ <field type="BooleanField" name="enable_comments">True</field>
+ </object>
+ <object pk="2" model="comment_tests.entry">
+ <field type="CharField" name="title">XYZ</field>
+ <field type="TextField" name="body">Text here</field>
+ <field type="DateField" name="pub_date">2008-01-02</field>
+ <field type="BooleanField" name="enable_comments">False</field>
+ </object>
+</django-objects>
diff --git a/tests/comment_tests/models.py b/tests/comment_tests/models.py
new file mode 100644
index 0000000000..472b66decd
--- /dev/null
+++ b/tests/comment_tests/models.py
@@ -0,0 +1,39 @@
+"""
+Comments may be attached to any object. See the comment documentation for
+more information.
+"""
+
+from __future__ import unicode_literals
+
+from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
+
+
+@python_2_unicode_compatible
+class Author(models.Model):
+ first_name = models.CharField(max_length=30)
+ last_name = models.CharField(max_length=30)
+
+ def __str__(self):
+ return '%s %s' % (self.first_name, self.last_name)
+
+@python_2_unicode_compatible
+class Article(models.Model):
+ author = models.ForeignKey(Author)
+ headline = models.CharField(max_length=100)
+
+ def __str__(self):
+ return self.headline
+
+@python_2_unicode_compatible
+class Entry(models.Model):
+ title = models.CharField(max_length=250)
+ body = models.TextField()
+ pub_date = models.DateField()
+ enable_comments = models.BooleanField()
+
+ def __str__(self):
+ return self.title
+
+class Book(models.Model):
+ dewey_decimal = models.DecimalField(primary_key=True, decimal_places=2, max_digits=5)
diff --git a/tests/comment_tests/tests/__init__.py b/tests/comment_tests/tests/__init__.py
new file mode 100644
index 0000000000..f80b29e17b
--- /dev/null
+++ b/tests/comment_tests/tests/__init__.py
@@ -0,0 +1,95 @@
+from __future__ import absolute_import
+
+from django.contrib.auth.models import User
+from django.contrib.comments.forms import CommentForm
+from django.contrib.comments.models import Comment
+from django.contrib.contenttypes.models import ContentType
+from django.contrib.sites.models import Site
+from django.test import TestCase
+from django.test.utils import override_settings
+
+from ..models import Article, Author
+
+# Shortcut
+CT = ContentType.objects.get_for_model
+
+# Helper base class for comment tests that need data.
+@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.UnsaltedMD5PasswordHasher',))
+class CommentTestCase(TestCase):
+ fixtures = ["comment_tests"]
+ urls = 'regressiontests.comment_tests.urls_default'
+
+ def createSomeComments(self):
+ # Two anonymous comments on two different objects
+ c1 = Comment.objects.create(
+ content_type = CT(Article),
+ object_pk = "1",
+ user_name = "Joe Somebody",
+ user_email = "jsomebody@example.com",
+ user_url = "http://example.com/~joe/",
+ comment = "First!",
+ site = Site.objects.get_current(),
+ )
+ c2 = Comment.objects.create(
+ content_type = CT(Author),
+ object_pk = "1",
+ user_name = "Joe Somebody",
+ user_email = "jsomebody@example.com",
+ user_url = "http://example.com/~joe/",
+ comment = "First here, too!",
+ site = Site.objects.get_current(),
+ )
+
+ # Two authenticated comments: one on the same Article, and
+ # one on a different Author
+ user = User.objects.create(
+ username = "frank_nobody",
+ first_name = "Frank",
+ last_name = "Nobody",
+ email = "fnobody@example.com",
+ password = "",
+ is_staff = False,
+ is_active = True,
+ is_superuser = False,
+ )
+ c3 = Comment.objects.create(
+ content_type = CT(Article),
+ object_pk = "1",
+ user = user,
+ user_url = "http://example.com/~frank/",
+ comment = "Damn, I wanted to be first.",
+ site = Site.objects.get_current(),
+ )
+ c4 = Comment.objects.create(
+ content_type = CT(Author),
+ object_pk = "2",
+ user = user,
+ user_url = "http://example.com/~frank/",
+ comment = "You get here first, too?",
+ site = Site.objects.get_current(),
+ )
+
+ return c1, c2, c3, c4
+
+ def getData(self):
+ return {
+ 'name' : 'Jim Bob',
+ 'email' : 'jim.bob@example.com',
+ 'url' : '',
+ 'comment' : 'This is my comment',
+ }
+
+ def getValidData(self, obj):
+ f = CommentForm(obj)
+ d = self.getData()
+ d.update(f.initial)
+ return d
+
+from regressiontests.comment_tests.tests.app_api_tests import *
+from regressiontests.comment_tests.tests.feed_tests import *
+from regressiontests.comment_tests.tests.model_tests import *
+from regressiontests.comment_tests.tests.comment_form_tests import *
+from regressiontests.comment_tests.tests.templatetag_tests import *
+from regressiontests.comment_tests.tests.comment_view_tests import *
+from regressiontests.comment_tests.tests.moderation_view_tests import *
+from regressiontests.comment_tests.tests.comment_utils_moderators_tests import *
diff --git a/tests/comment_tests/tests/app_api_tests.py b/tests/comment_tests/tests/app_api_tests.py
new file mode 100644
index 0000000000..a756068790
--- /dev/null
+++ b/tests/comment_tests/tests/app_api_tests.py
@@ -0,0 +1,80 @@
+from __future__ import absolute_import
+
+from django.conf import settings
+from django.contrib import comments
+from django.contrib.comments.models import Comment
+from django.contrib.comments.forms import CommentForm
+from django.core.exceptions import ImproperlyConfigured
+from django.test.utils import override_settings
+from django.utils import six
+
+from . import CommentTestCase
+
+
+class CommentAppAPITests(CommentTestCase):
+ """Tests for the "comment app" API"""
+
+ def testGetCommentApp(self):
+ self.assertEqual(comments.get_comment_app(), comments)
+
+ @override_settings(
+ COMMENTS_APP='missing_app',
+ INSTALLED_APPS=list(settings.INSTALLED_APPS) + ['missing_app'],
+ )
+ def testGetMissingCommentApp(self):
+ with six.assertRaisesRegex(self, ImproperlyConfigured, 'missing_app'):
+ _ = comments.get_comment_app()
+
+ def testGetForm(self):
+ self.assertEqual(comments.get_form(), CommentForm)
+
+ def testGetFormTarget(self):
+ self.assertEqual(comments.get_form_target(), "/post/")
+
+ def testGetFlagURL(self):
+ c = Comment(id=12345)
+ self.assertEqual(comments.get_flag_url(c), "/flag/12345/")
+
+ def getGetDeleteURL(self):
+ c = Comment(id=12345)
+ self.assertEqual(comments.get_delete_url(c), "/delete/12345/")
+
+ def getGetApproveURL(self):
+ c = Comment(id=12345)
+ self.assertEqual(comments.get_approve_url(c), "/approve/12345/")
+
+
+@override_settings(
+ COMMENTS_APP='regressiontests.comment_tests.custom_comments',
+ INSTALLED_APPS=list(settings.INSTALLED_APPS) + [
+ 'regressiontests.comment_tests.custom_comments'],
+)
+class CustomCommentTest(CommentTestCase):
+ urls = 'regressiontests.comment_tests.urls'
+
+ def testGetCommentApp(self):
+ from regressiontests.comment_tests import custom_comments
+ self.assertEqual(comments.get_comment_app(), custom_comments)
+
+ def testGetModel(self):
+ from regressiontests.comment_tests.custom_comments.models import CustomComment
+ self.assertEqual(comments.get_model(), CustomComment)
+
+ def testGetForm(self):
+ from regressiontests.comment_tests.custom_comments.forms import CustomCommentForm
+ self.assertEqual(comments.get_form(), CustomCommentForm)
+
+ def testGetFormTarget(self):
+ self.assertEqual(comments.get_form_target(), "/post/")
+
+ def testGetFlagURL(self):
+ c = Comment(id=12345)
+ self.assertEqual(comments.get_flag_url(c), "/flag/12345/")
+
+ def getGetDeleteURL(self):
+ c = Comment(id=12345)
+ self.assertEqual(comments.get_delete_url(c), "/delete/12345/")
+
+ def getGetApproveURL(self):
+ c = Comment(id=12345)
+ self.assertEqual(comments.get_approve_url(c), "/approve/12345/")
diff --git a/tests/comment_tests/tests/comment_form_tests.py b/tests/comment_tests/tests/comment_form_tests.py
new file mode 100644
index 0000000000..39ba57928d
--- /dev/null
+++ b/tests/comment_tests/tests/comment_form_tests.py
@@ -0,0 +1,85 @@
+from __future__ import absolute_import
+
+import time
+
+from django.conf import settings
+from django.contrib.comments.forms import CommentForm
+from django.contrib.comments.models import Comment
+
+from . import CommentTestCase
+from ..models import Article
+
+
+class CommentFormTests(CommentTestCase):
+ def testInit(self):
+ f = CommentForm(Article.objects.get(pk=1))
+ self.assertEqual(f.initial['content_type'], str(Article._meta))
+ self.assertEqual(f.initial['object_pk'], "1")
+ self.assertNotEqual(f.initial['security_hash'], None)
+ self.assertNotEqual(f.initial['timestamp'], None)
+
+ def testValidPost(self):
+ a = Article.objects.get(pk=1)
+ f = CommentForm(a, data=self.getValidData(a))
+ self.assertTrue(f.is_valid(), f.errors)
+ return f
+
+ def tamperWithForm(self, **kwargs):
+ a = Article.objects.get(pk=1)
+ d = self.getValidData(a)
+ d.update(kwargs)
+ f = CommentForm(Article.objects.get(pk=1), data=d)
+ self.assertFalse(f.is_valid())
+ return f
+
+ def testHoneypotTampering(self):
+ self.tamperWithForm(honeypot="I am a robot")
+
+ def testTimestampTampering(self):
+ self.tamperWithForm(timestamp=str(time.time() - 28800))
+
+ def testSecurityHashTampering(self):
+ self.tamperWithForm(security_hash="Nobody expects the Spanish Inquisition!")
+
+ def testContentTypeTampering(self):
+ self.tamperWithForm(content_type="auth.user")
+
+ def testObjectPKTampering(self):
+ self.tamperWithForm(object_pk="3")
+
+ def testSecurityErrors(self):
+ f = self.tamperWithForm(honeypot="I am a robot")
+ self.assertTrue("honeypot" in f.security_errors())
+
+ def testGetCommentObject(self):
+ f = self.testValidPost()
+ c = f.get_comment_object()
+ self.assertTrue(isinstance(c, Comment))
+ self.assertEqual(c.content_object, Article.objects.get(pk=1))
+ self.assertEqual(c.comment, "This is my comment")
+ c.save()
+ self.assertEqual(Comment.objects.count(), 1)
+
+ def testProfanities(self):
+ """Test COMMENTS_ALLOW_PROFANITIES and PROFANITIES_LIST settings"""
+ a = Article.objects.get(pk=1)
+ d = self.getValidData(a)
+
+ # Save settings in case other tests need 'em
+ saved = settings.PROFANITIES_LIST, settings.COMMENTS_ALLOW_PROFANITIES
+
+ # Don't wanna swear in the unit tests if we don't have to...
+ settings.PROFANITIES_LIST = ["rooster"]
+
+ # Try with COMMENTS_ALLOW_PROFANITIES off
+ settings.COMMENTS_ALLOW_PROFANITIES = False
+ f = CommentForm(a, data=dict(d, comment="What a rooster!"))
+ self.assertFalse(f.is_valid())
+
+ # Now with COMMENTS_ALLOW_PROFANITIES on
+ settings.COMMENTS_ALLOW_PROFANITIES = True
+ f = CommentForm(a, data=dict(d, comment="What a rooster!"))
+ self.assertTrue(f.is_valid())
+
+ # Restore settings
+ settings.PROFANITIES_LIST, settings.COMMENTS_ALLOW_PROFANITIES = saved
diff --git a/tests/comment_tests/tests/comment_utils_moderators_tests.py b/tests/comment_tests/tests/comment_utils_moderators_tests.py
new file mode 100644
index 0000000000..eab87ef53b
--- /dev/null
+++ b/tests/comment_tests/tests/comment_utils_moderators_tests.py
@@ -0,0 +1,97 @@
+from __future__ import absolute_import
+
+from django.contrib.comments.models import Comment
+from django.contrib.comments.moderation import (moderator, CommentModerator,
+ AlreadyModerated)
+from django.core import mail
+
+from . import CommentTestCase
+from ..models import Entry
+
+
+class EntryModerator1(CommentModerator):
+ email_notification = True
+
+class EntryModerator2(CommentModerator):
+ enable_field = 'enable_comments'
+
+class EntryModerator3(CommentModerator):
+ auto_close_field = 'pub_date'
+ close_after = 7
+
+class EntryModerator4(CommentModerator):
+ auto_moderate_field = 'pub_date'
+ moderate_after = 7
+
+class EntryModerator5(CommentModerator):
+ auto_moderate_field = 'pub_date'
+ moderate_after = 0
+
+class EntryModerator6(CommentModerator):
+ auto_close_field = 'pub_date'
+ close_after = 0
+
+class CommentUtilsModeratorTests(CommentTestCase):
+ fixtures = ["comment_utils.xml"]
+
+ def createSomeComments(self):
+ # Tests for the moderation signals must actually post data
+ # through the comment views, because only the comment views
+ # emit the custom signals moderation listens for.
+ e = Entry.objects.get(pk=1)
+ data = self.getValidData(e)
+
+ self.client.post("/post/", data, REMOTE_ADDR="1.2.3.4")
+
+ # We explicitly do a try/except to get the comment we've just
+ # posted because moderation may have disallowed it, in which
+ # case we can just return it as None.
+ try:
+ c1 = Comment.objects.all()[0]
+ except IndexError:
+ c1 = None
+
+ self.client.post("/post/", data, REMOTE_ADDR="1.2.3.4")
+
+ try:
+ c2 = Comment.objects.all()[0]
+ except IndexError:
+ c2 = None
+ return c1, c2
+
+ def tearDown(self):
+ moderator.unregister(Entry)
+
+ def testRegisterExistingModel(self):
+ moderator.register(Entry, EntryModerator1)
+ self.assertRaises(AlreadyModerated, moderator.register, Entry, EntryModerator1)
+
+ def testEmailNotification(self):
+ moderator.register(Entry, EntryModerator1)
+ self.createSomeComments()
+ self.assertEqual(len(mail.outbox), 2)
+
+ def testCommentsEnabled(self):
+ moderator.register(Entry, EntryModerator2)
+ self.createSomeComments()
+ self.assertEqual(Comment.objects.all().count(), 1)
+
+ def testAutoCloseField(self):
+ moderator.register(Entry, EntryModerator3)
+ self.createSomeComments()
+ self.assertEqual(Comment.objects.all().count(), 0)
+
+ def testAutoModerateField(self):
+ moderator.register(Entry, EntryModerator4)
+ c1, c2 = self.createSomeComments()
+ self.assertEqual(c2.is_public, False)
+
+ def testAutoModerateFieldImmediate(self):
+ moderator.register(Entry, EntryModerator5)
+ c1, c2 = self.createSomeComments()
+ self.assertEqual(c2.is_public, False)
+
+ def testAutoCloseFieldImmediate(self):
+ moderator.register(Entry, EntryModerator6)
+ c1, c2 = self.createSomeComments()
+ self.assertEqual(Comment.objects.all().count(), 0) \ No newline at end of file
diff --git a/tests/comment_tests/tests/comment_view_tests.py b/tests/comment_tests/tests/comment_view_tests.py
new file mode 100644
index 0000000000..0d994d3af8
--- /dev/null
+++ b/tests/comment_tests/tests/comment_view_tests.py
@@ -0,0 +1,324 @@
+from __future__ import absolute_import, unicode_literals
+
+import re
+
+from django.conf import settings
+from django.contrib.auth.models import User
+from django.contrib.comments import signals
+from django.contrib.comments.models import Comment
+
+from . import CommentTestCase
+from ..models import Article, Book
+
+
+post_redirect_re = re.compile(r'^http://testserver/posted/\?c=(?P<pk>\d+$)')
+
+class CommentViewTests(CommentTestCase):
+
+ def testPostCommentHTTPMethods(self):
+ a = Article.objects.get(pk=1)
+ data = self.getValidData(a)
+ response = self.client.get("/post/", data)
+ self.assertEqual(response.status_code, 405)
+ self.assertEqual(response["Allow"], "POST")
+
+ def testPostCommentMissingCtype(self):
+ a = Article.objects.get(pk=1)
+ data = self.getValidData(a)
+ del data["content_type"]
+ response = self.client.post("/post/", data)
+ self.assertEqual(response.status_code, 400)
+
+ def testPostCommentBadCtype(self):
+ a = Article.objects.get(pk=1)
+ data = self.getValidData(a)
+ data["content_type"] = "Nobody expects the Spanish Inquisition!"
+ response = self.client.post("/post/", data)
+ self.assertEqual(response.status_code, 400)
+
+ def testPostCommentMissingObjectPK(self):
+ a = Article.objects.get(pk=1)
+ data = self.getValidData(a)
+ del data["object_pk"]
+ response = self.client.post("/post/", data)
+ self.assertEqual(response.status_code, 400)
+
+ def testPostCommentBadObjectPK(self):
+ a = Article.objects.get(pk=1)
+ data = self.getValidData(a)
+ data["object_pk"] = "14"
+ response = self.client.post("/post/", data)
+ self.assertEqual(response.status_code, 400)
+
+ def testPostInvalidIntegerPK(self):
+ a = Article.objects.get(pk=1)
+ data = self.getValidData(a)
+ data["comment"] = "This is another comment"
+ data["object_pk"] = '\ufffd'
+ response = self.client.post("/post/", data)
+ self.assertEqual(response.status_code, 400)
+
+ def testPostInvalidDecimalPK(self):
+ b = Book.objects.get(pk='12.34')
+ data = self.getValidData(b)
+ data["comment"] = "This is another comment"
+ data["object_pk"] = 'cookies'
+ response = self.client.post("/post/", data)
+ self.assertEqual(response.status_code, 400)
+
+ def testCommentPreview(self):
+ a = Article.objects.get(pk=1)
+ data = self.getValidData(a)
+ data["preview"] = "Preview"
+ response = self.client.post("/post/", data)
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, "comments/preview.html")
+
+ def testHashTampering(self):
+ a = Article.objects.get(pk=1)
+ data = self.getValidData(a)
+ data["security_hash"] = "Nobody expects the Spanish Inquisition!"
+ response = self.client.post("/post/", data)
+ self.assertEqual(response.status_code, 400)
+
+ def testDebugCommentErrors(self):
+ """The debug error template should be shown only if DEBUG is True"""
+ olddebug = settings.DEBUG
+
+ settings.DEBUG = True
+ a = Article.objects.get(pk=1)
+ data = self.getValidData(a)
+ data["security_hash"] = "Nobody expects the Spanish Inquisition!"
+ response = self.client.post("/post/", data)
+ self.assertEqual(response.status_code, 400)
+ self.assertTemplateUsed(response, "comments/400-debug.html")
+
+ settings.DEBUG = False
+ response = self.client.post("/post/", data)
+ self.assertEqual(response.status_code, 400)
+ self.assertTemplateNotUsed(response, "comments/400-debug.html")
+
+ settings.DEBUG = olddebug
+
+ def testCreateValidComment(self):
+ address = "1.2.3.4"
+ a = Article.objects.get(pk=1)
+ data = self.getValidData(a)
+ self.response = self.client.post("/post/", data, REMOTE_ADDR=address)
+ self.assertEqual(self.response.status_code, 302)
+ self.assertEqual(Comment.objects.count(), 1)
+ c = Comment.objects.all()[0]
+ self.assertEqual(c.ip_address, address)
+ self.assertEqual(c.comment, "This is my comment")
+
+ def testCreateValidCommentIPv6(self):
+ """
+ Test creating a valid comment with a long IPv6 address.
+ Note that this test should fail when Comment.ip_address is an IPAddress instead of a GenericIPAddress,
+ but does not do so on SQLite or PostgreSQL, because they use the TEXT and INET types, which already
+ allow storing an IPv6 address internally.
+ """
+ address = "2a02::223:6cff:fe8a:2e8a"
+ a = Article.objects.get(pk=1)
+ data = self.getValidData(a)
+ self.response = self.client.post("/post/", data, REMOTE_ADDR=address)
+ self.assertEqual(self.response.status_code, 302)
+ self.assertEqual(Comment.objects.count(), 1)
+ c = Comment.objects.all()[0]
+ self.assertEqual(c.ip_address, address)
+ self.assertEqual(c.comment, "This is my comment")
+
+ def testCreateValidCommentIPv6Unpack(self):
+ address = "::ffff:18.52.18.52"
+ a = Article.objects.get(pk=1)
+ data = self.getValidData(a)
+ self.response = self.client.post("/post/", data, REMOTE_ADDR=address)
+ self.assertEqual(self.response.status_code, 302)
+ self.assertEqual(Comment.objects.count(), 1)
+ c = Comment.objects.all()[0]
+ # We trim the '::ffff:' bit off because it is an IPv4 addr
+ self.assertEqual(c.ip_address, address[7:])
+ self.assertEqual(c.comment, "This is my comment")
+
+ def testPostAsAuthenticatedUser(self):
+ a = Article.objects.get(pk=1)
+ data = self.getValidData(a)
+ data['name'] = data['email'] = ''
+ self.client.login(username="normaluser", password="normaluser")
+ self.response = self.client.post("/post/", data, REMOTE_ADDR="1.2.3.4")
+ self.assertEqual(self.response.status_code, 302)
+ self.assertEqual(Comment.objects.count(), 1)
+ c = Comment.objects.all()[0]
+ self.assertEqual(c.ip_address, "1.2.3.4")
+ u = User.objects.get(username='normaluser')
+ self.assertEqual(c.user, u)
+ self.assertEqual(c.user_name, u.get_full_name())
+ self.assertEqual(c.user_email, u.email)
+
+ def testPostAsAuthenticatedUserWithoutFullname(self):
+ """
+ Check that the user's name in the comment is populated for
+ authenticated users without first_name and last_name.
+ """
+ user = User.objects.create_user(username='jane_other',
+ email='jane@example.com', password='jane_other')
+ a = Article.objects.get(pk=1)
+ data = self.getValidData(a)
+ data['name'] = data['email'] = ''
+ self.client.login(username="jane_other", password="jane_other")
+ self.response = self.client.post("/post/", data, REMOTE_ADDR="1.2.3.4")
+ c = Comment.objects.get(user=user)
+ self.assertEqual(c.ip_address, "1.2.3.4")
+ self.assertEqual(c.user_name, 'jane_other')
+ user.delete()
+
+ def testPreventDuplicateComments(self):
+ """Prevent posting the exact same comment twice"""
+ a = Article.objects.get(pk=1)
+ data = self.getValidData(a)
+ self.client.post("/post/", data)
+ self.client.post("/post/", data)
+ self.assertEqual(Comment.objects.count(), 1)
+
+ # This should not trigger the duplicate prevention
+ self.client.post("/post/", dict(data, comment="My second comment."))
+ self.assertEqual(Comment.objects.count(), 2)
+
+ def testCommentSignals(self):
+ """Test signals emitted by the comment posting view"""
+
+ # callback
+ def receive(sender, **kwargs):
+ self.assertEqual(kwargs['comment'].comment, "This is my comment")
+ self.assertTrue('request' in kwargs)
+ received_signals.append(kwargs.get('signal'))
+
+ # Connect signals and keep track of handled ones
+ received_signals = []
+ expected_signals = [
+ signals.comment_will_be_posted, signals.comment_was_posted
+ ]
+ for signal in expected_signals:
+ signal.connect(receive)
+
+ # Post a comment and check the signals
+ self.testCreateValidComment()
+ self.assertEqual(received_signals, expected_signals)
+
+ for signal in expected_signals:
+ signal.disconnect(receive)
+
+ def testWillBePostedSignal(self):
+ """
+ Test that the comment_will_be_posted signal can prevent the comment from
+ actually getting saved
+ """
+ def receive(sender, **kwargs): return False
+ signals.comment_will_be_posted.connect(receive, dispatch_uid="comment-test")
+ a = Article.objects.get(pk=1)
+ data = self.getValidData(a)
+ response = self.client.post("/post/", data)
+ self.assertEqual(response.status_code, 400)
+ self.assertEqual(Comment.objects.count(), 0)
+ signals.comment_will_be_posted.disconnect(dispatch_uid="comment-test")
+
+ def testWillBePostedSignalModifyComment(self):
+ """
+ Test that the comment_will_be_posted signal can modify a comment before
+ it gets posted
+ """
+ def receive(sender, **kwargs):
+ # a bad but effective spam filter :)...
+ kwargs['comment'].is_public = False
+
+ signals.comment_will_be_posted.connect(receive)
+ self.testCreateValidComment()
+ c = Comment.objects.all()[0]
+ self.assertFalse(c.is_public)
+
+ def testCommentNext(self):
+ """Test the different "next" actions the comment view can take"""
+ a = Article.objects.get(pk=1)
+ data = self.getValidData(a)
+ response = self.client.post("/post/", data)
+ location = response["Location"]
+ match = post_redirect_re.match(location)
+ self.assertTrue(match != None, "Unexpected redirect location: %s" % location)
+
+ data["next"] = "/somewhere/else/"
+ data["comment"] = "This is another comment"
+ response = self.client.post("/post/", data)
+ location = response["Location"]
+ match = re.search(r"^http://testserver/somewhere/else/\?c=\d+$", location)
+ self.assertTrue(match != None, "Unexpected redirect location: %s" % location)
+
+ data["next"] = "http://badserver/somewhere/else/"
+ data["comment"] = "This is another comment with an unsafe next url"
+ response = self.client.post("/post/", data)
+ location = response["Location"]
+ match = post_redirect_re.match(location)
+ self.assertTrue(match != None, "Unsafe redirection to: %s" % location)
+
+ def testCommentDoneView(self):
+ a = Article.objects.get(pk=1)
+ data = self.getValidData(a)
+ response = self.client.post("/post/", data)
+ location = response["Location"]
+ match = post_redirect_re.match(location)
+ self.assertTrue(match != None, "Unexpected redirect location: %s" % location)
+ pk = int(match.group('pk'))
+ response = self.client.get(location)
+ self.assertTemplateUsed(response, "comments/posted.html")
+ self.assertEqual(response.context[0]["comment"], Comment.objects.get(pk=pk))
+
+ def testCommentNextWithQueryString(self):
+ """
+ The `next` key needs to handle already having a query string (#10585)
+ """
+ a = Article.objects.get(pk=1)
+ data = self.getValidData(a)
+ data["next"] = "/somewhere/else/?foo=bar"
+ data["comment"] = "This is another comment"
+ response = self.client.post("/post/", data)
+ location = response["Location"]
+ match = re.search(r"^http://testserver/somewhere/else/\?foo=bar&c=\d+$", location)
+ self.assertTrue(match != None, "Unexpected redirect location: %s" % location)
+
+ def testCommentPostRedirectWithInvalidIntegerPK(self):
+ """
+ Tests that attempting to retrieve the location specified in the
+ post redirect, after adding some invalid data to the expected
+ querystring it ends with, doesn't cause a server error.
+ """
+ a = Article.objects.get(pk=1)
+ data = self.getValidData(a)
+ data["comment"] = "This is another comment"
+ response = self.client.post("/post/", data)
+ location = response["Location"]
+ broken_location = location + "\ufffd"
+ response = self.client.get(broken_location)
+ self.assertEqual(response.status_code, 200)
+
+ def testCommentNextWithQueryStringAndAnchor(self):
+ """
+ The `next` key needs to handle already having an anchor. Refs #13411.
+ """
+ # With a query string also.
+ a = Article.objects.get(pk=1)
+ data = self.getValidData(a)
+ data["next"] = "/somewhere/else/?foo=bar#baz"
+ data["comment"] = "This is another comment"
+ response = self.client.post("/post/", data)
+ location = response["Location"]
+ match = re.search(r"^http://testserver/somewhere/else/\?foo=bar&c=\d+#baz$", location)
+ self.assertTrue(match != None, "Unexpected redirect location: %s" % location)
+
+ # Without a query string
+ a = Article.objects.get(pk=1)
+ data = self.getValidData(a)
+ data["next"] = "/somewhere/else/#baz"
+ data["comment"] = "This is another comment"
+ response = self.client.post("/post/", data)
+ location = response["Location"]
+ match = re.search(r"^http://testserver/somewhere/else/\?c=\d+#baz$", location)
+ self.assertTrue(match != None, "Unexpected redirect location: %s" % location)
diff --git a/tests/comment_tests/tests/feed_tests.py b/tests/comment_tests/tests/feed_tests.py
new file mode 100644
index 0000000000..c93d7abf7d
--- /dev/null
+++ b/tests/comment_tests/tests/feed_tests.py
@@ -0,0 +1,53 @@
+from __future__ import absolute_import
+
+from xml.etree import ElementTree as ET
+
+from django.conf import settings
+from django.contrib.comments.models import Comment
+from django.contrib.contenttypes.models import ContentType
+from django.contrib.sites.models import Site
+
+from . import CommentTestCase
+from ..models import Article
+
+
+class CommentFeedTests(CommentTestCase):
+ urls = 'regressiontests.comment_tests.urls'
+ feed_url = '/rss/comments/'
+
+ def setUp(self):
+ site_2 = Site.objects.create(id=settings.SITE_ID+1,
+ domain="example2.com", name="example2.com")
+ # A comment for another site
+ c5 = Comment.objects.create(
+ content_type = ContentType.objects.get_for_model(Article),
+ object_pk = "1",
+ user_name = "Joe Somebody",
+ user_email = "jsomebody@example.com",
+ user_url = "http://example.com/~joe/",
+ comment = "A comment for the second site.",
+ site = site_2,
+ )
+
+ def test_feed(self):
+ response = self.client.get(self.feed_url)
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response['Content-Type'], 'application/rss+xml; charset=utf-8')
+
+ rss_elem = ET.fromstring(response.content)
+
+ self.assertEqual(rss_elem.tag, "rss")
+ self.assertEqual(rss_elem.attrib, {"version": "2.0"})
+
+ channel_elem = rss_elem.find("channel")
+
+ title_elem = channel_elem.find("title")
+ self.assertEqual(title_elem.text, "example.com comments")
+
+ link_elem = channel_elem.find("link")
+ self.assertEqual(link_elem.text, "http://example.com/")
+
+ atomlink_elem = channel_elem.find("{http://www.w3.org/2005/Atom}link")
+ self.assertEqual(atomlink_elem.attrib, {"href": "http://example.com/rss/comments/", "rel": "self"})
+
+ self.assertNotContains(response, "A comment for the second site.")
diff --git a/tests/comment_tests/tests/model_tests.py b/tests/comment_tests/tests/model_tests.py
new file mode 100644
index 0000000000..69c1a8118f
--- /dev/null
+++ b/tests/comment_tests/tests/model_tests.py
@@ -0,0 +1,58 @@
+from __future__ import absolute_import
+
+from django.contrib.comments.models import Comment
+
+from . import CommentTestCase
+from ..models import Author, Article
+
+
+class CommentModelTests(CommentTestCase):
+ def testSave(self):
+ for c in self.createSomeComments():
+ self.assertNotEqual(c.submit_date, None)
+
+ def testUserProperties(self):
+ c1, c2, c3, c4 = self.createSomeComments()
+ self.assertEqual(c1.name, "Joe Somebody")
+ self.assertEqual(c2.email, "jsomebody@example.com")
+ self.assertEqual(c3.name, "Frank Nobody")
+ self.assertEqual(c3.url, "http://example.com/~frank/")
+ self.assertEqual(c1.user, None)
+ self.assertEqual(c3.user, c4.user)
+
+class CommentManagerTests(CommentTestCase):
+
+ def testInModeration(self):
+ """Comments that aren't public are considered in moderation"""
+ c1, c2, c3, c4 = self.createSomeComments()
+ c1.is_public = False
+ c2.is_public = False
+ c1.save()
+ c2.save()
+ moderated_comments = list(Comment.objects.in_moderation().order_by("id"))
+ self.assertEqual(moderated_comments, [c1, c2])
+
+ def testRemovedCommentsNotInModeration(self):
+ """Removed comments are not considered in moderation"""
+ c1, c2, c3, c4 = self.createSomeComments()
+ c1.is_public = False
+ c2.is_public = False
+ c2.is_removed = True
+ c1.save()
+ c2.save()
+ moderated_comments = list(Comment.objects.in_moderation())
+ self.assertEqual(moderated_comments, [c1])
+
+ def testForModel(self):
+ c1, c2, c3, c4 = self.createSomeComments()
+ article_comments = list(Comment.objects.for_model(Article).order_by("id"))
+ author_comments = list(Comment.objects.for_model(Author.objects.get(pk=1)))
+ self.assertEqual(article_comments, [c1, c3])
+ self.assertEqual(author_comments, [c2])
+
+ def testPrefetchRelated(self):
+ c1, c2, c3, c4 = self.createSomeComments()
+ # one for comments, one for Articles, one for Author
+ with self.assertNumQueries(3):
+ qs = Comment.objects.prefetch_related('content_object')
+ [c.content_object for c in qs]
diff --git a/tests/comment_tests/tests/moderation_view_tests.py b/tests/comment_tests/tests/moderation_view_tests.py
new file mode 100644
index 0000000000..c932fed2d2
--- /dev/null
+++ b/tests/comment_tests/tests/moderation_view_tests.py
@@ -0,0 +1,315 @@
+from __future__ import absolute_import, unicode_literals
+
+from django.contrib.auth.models import User, Permission
+from django.contrib.comments import signals
+from django.contrib.comments.models import Comment, CommentFlag
+from django.contrib.contenttypes.models import ContentType
+from django.utils import translation
+
+from . import CommentTestCase
+
+
+class FlagViewTests(CommentTestCase):
+
+ def testFlagGet(self):
+ """GET the flag view: render a confirmation page."""
+ comments = self.createSomeComments()
+ pk = comments[0].pk
+ self.client.login(username="normaluser", password="normaluser")
+ response = self.client.get("/flag/%d/" % pk)
+ self.assertTemplateUsed(response, "comments/flag.html")
+
+ def testFlagPost(self):
+ """POST the flag view: actually flag the view (nice for XHR)"""
+ comments = self.createSomeComments()
+ pk = comments[0].pk
+ self.client.login(username="normaluser", password="normaluser")
+ response = self.client.post("/flag/%d/" % pk)
+ self.assertEqual(response["Location"], "http://testserver/flagged/?c=%d" % pk)
+ c = Comment.objects.get(pk=pk)
+ self.assertEqual(c.flags.filter(flag=CommentFlag.SUGGEST_REMOVAL).count(), 1)
+ return c
+
+ def testFlagPostNext(self):
+ """
+ POST the flag view, explicitly providing a next url.
+ """
+ comments = self.createSomeComments()
+ pk = comments[0].pk
+ self.client.login(username="normaluser", password="normaluser")
+ response = self.client.post("/flag/%d/" % pk, {'next': "/go/here/"})
+ self.assertEqual(response["Location"],
+ "http://testserver/go/here/?c=%d" % pk)
+
+ def testFlagPostUnsafeNext(self):
+ """
+ POSTing to the flag view with an unsafe next url will ignore the
+ provided url when redirecting.
+ """
+ comments = self.createSomeComments()
+ pk = comments[0].pk
+ self.client.login(username="normaluser", password="normaluser")
+ response = self.client.post("/flag/%d/" % pk,
+ {'next': "http://elsewhere/bad"})
+ self.assertEqual(response["Location"],
+ "http://testserver/flagged/?c=%d" % pk)
+
+ def testFlagPostTwice(self):
+ """Users don't get to flag comments more than once."""
+ c = self.testFlagPost()
+ self.client.post("/flag/%d/" % c.pk)
+ self.client.post("/flag/%d/" % c.pk)
+ self.assertEqual(c.flags.filter(flag=CommentFlag.SUGGEST_REMOVAL).count(), 1)
+
+ def testFlagAnon(self):
+ """GET/POST the flag view while not logged in: redirect to log in."""
+ comments = self.createSomeComments()
+ pk = comments[0].pk
+ response = self.client.get("/flag/%d/" % pk)
+ self.assertEqual(response["Location"], "http://testserver/accounts/login/?next=/flag/%d/" % pk)
+ response = self.client.post("/flag/%d/" % pk)
+ self.assertEqual(response["Location"], "http://testserver/accounts/login/?next=/flag/%d/" % pk)
+
+ def testFlaggedView(self):
+ comments = self.createSomeComments()
+ pk = comments[0].pk
+ response = self.client.get("/flagged/", data={"c": pk})
+ self.assertTemplateUsed(response, "comments/flagged.html")
+
+ def testFlagSignals(self):
+ """Test signals emitted by the comment flag view"""
+
+ # callback
+ def receive(sender, **kwargs):
+ self.assertEqual(kwargs['flag'].flag, CommentFlag.SUGGEST_REMOVAL)
+ self.assertEqual(kwargs['request'].user.username, "normaluser")
+ received_signals.append(kwargs.get('signal'))
+
+ # Connect signals and keep track of handled ones
+ received_signals = []
+ signals.comment_was_flagged.connect(receive)
+
+ # Post a comment and check the signals
+ self.testFlagPost()
+ self.assertEqual(received_signals, [signals.comment_was_flagged])
+
+ signals.comment_was_flagged.disconnect(receive)
+
+def makeModerator(username):
+ u = User.objects.get(username=username)
+ ct = ContentType.objects.get_for_model(Comment)
+ p = Permission.objects.get(content_type=ct, codename="can_moderate")
+ u.user_permissions.add(p)
+
+class DeleteViewTests(CommentTestCase):
+
+ def testDeletePermissions(self):
+ """The delete view should only be accessible to 'moderators'"""
+ comments = self.createSomeComments()
+ pk = comments[0].pk
+ self.client.login(username="normaluser", password="normaluser")
+ response = self.client.get("/delete/%d/" % pk)
+ self.assertEqual(response["Location"], "http://testserver/accounts/login/?next=/delete/%d/" % pk)
+
+ makeModerator("normaluser")
+ response = self.client.get("/delete/%d/" % pk)
+ self.assertEqual(response.status_code, 200)
+
+ def testDeletePost(self):
+ """POSTing the delete view should mark the comment as removed"""
+ comments = self.createSomeComments()
+ pk = comments[0].pk
+ makeModerator("normaluser")
+ self.client.login(username="normaluser", password="normaluser")
+ response = self.client.post("/delete/%d/" % pk)
+ self.assertEqual(response["Location"], "http://testserver/deleted/?c=%d" % pk)
+ c = Comment.objects.get(pk=pk)
+ self.assertTrue(c.is_removed)
+ self.assertEqual(c.flags.filter(flag=CommentFlag.MODERATOR_DELETION, user__username="normaluser").count(), 1)
+
+ def testDeletePostNext(self):
+ """
+ POSTing the delete view will redirect to an explicitly provided a next
+ url.
+ """
+ comments = self.createSomeComments()
+ pk = comments[0].pk
+ makeModerator("normaluser")
+ self.client.login(username="normaluser", password="normaluser")
+ response = self.client.post("/delete/%d/" % pk, {'next': "/go/here/"})
+ self.assertEqual(response["Location"],
+ "http://testserver/go/here/?c=%d" % pk)
+
+ def testDeletePostUnsafeNext(self):
+ """
+ POSTing to the delete view with an unsafe next url will ignore the
+ provided url when redirecting.
+ """
+ comments = self.createSomeComments()
+ pk = comments[0].pk
+ makeModerator("normaluser")
+ self.client.login(username="normaluser", password="normaluser")
+ response = self.client.post("/delete/%d/" % pk,
+ {'next': "http://elsewhere/bad"})
+ self.assertEqual(response["Location"],
+ "http://testserver/deleted/?c=%d" % pk)
+
+ def testDeleteSignals(self):
+ def receive(sender, **kwargs):
+ received_signals.append(kwargs.get('signal'))
+
+ # Connect signals and keep track of handled ones
+ received_signals = []
+ signals.comment_was_flagged.connect(receive)
+
+ # Post a comment and check the signals
+ self.testDeletePost()
+ self.assertEqual(received_signals, [signals.comment_was_flagged])
+
+ signals.comment_was_flagged.disconnect(receive)
+
+ def testDeletedView(self):
+ comments = self.createSomeComments()
+ pk = comments[0].pk
+ response = self.client.get("/deleted/", data={"c": pk})
+ self.assertTemplateUsed(response, "comments/deleted.html")
+
+class ApproveViewTests(CommentTestCase):
+
+ def testApprovePermissions(self):
+ """The approve view should only be accessible to 'moderators'"""
+ comments = self.createSomeComments()
+ pk = comments[0].pk
+ self.client.login(username="normaluser", password="normaluser")
+ response = self.client.get("/approve/%d/" % pk)
+ self.assertEqual(response["Location"], "http://testserver/accounts/login/?next=/approve/%d/" % pk)
+
+ makeModerator("normaluser")
+ response = self.client.get("/approve/%d/" % pk)
+ self.assertEqual(response.status_code, 200)
+
+ def testApprovePost(self):
+ """POSTing the approve view should mark the comment as removed"""
+ c1, c2, c3, c4 = self.createSomeComments()
+ c1.is_public = False; c1.save()
+
+ makeModerator("normaluser")
+ self.client.login(username="normaluser", password="normaluser")
+ response = self.client.post("/approve/%d/" % c1.pk)
+ self.assertEqual(response["Location"], "http://testserver/approved/?c=%d" % c1.pk)
+ c = Comment.objects.get(pk=c1.pk)
+ self.assertTrue(c.is_public)
+ self.assertEqual(c.flags.filter(flag=CommentFlag.MODERATOR_APPROVAL, user__username="normaluser").count(), 1)
+
+ def testApprovePostNext(self):
+ """
+ POSTing the approve view will redirect to an explicitly provided a next
+ url.
+ """
+ c1, c2, c3, c4 = self.createSomeComments()
+ c1.is_public = False; c1.save()
+
+ makeModerator("normaluser")
+ self.client.login(username="normaluser", password="normaluser")
+ response = self.client.post("/approve/%d/" % c1.pk,
+ {'next': "/go/here/"})
+ self.assertEqual(response["Location"],
+ "http://testserver/go/here/?c=%d" % c1.pk)
+
+ def testApprovePostUnsafeNext(self):
+ """
+ POSTing to the approve view with an unsafe next url will ignore the
+ provided url when redirecting.
+ """
+ c1, c2, c3, c4 = self.createSomeComments()
+ c1.is_public = False; c1.save()
+
+ makeModerator("normaluser")
+ self.client.login(username="normaluser", password="normaluser")
+ response = self.client.post("/approve/%d/" % c1.pk,
+ {'next': "http://elsewhere/bad"})
+ self.assertEqual(response["Location"],
+ "http://testserver/approved/?c=%d" % c1.pk)
+
+ def testApproveSignals(self):
+ def receive(sender, **kwargs):
+ received_signals.append(kwargs.get('signal'))
+
+ # Connect signals and keep track of handled ones
+ received_signals = []
+ signals.comment_was_flagged.connect(receive)
+
+ # Post a comment and check the signals
+ self.testApprovePost()
+ self.assertEqual(received_signals, [signals.comment_was_flagged])
+
+ signals.comment_was_flagged.disconnect(receive)
+
+ def testApprovedView(self):
+ comments = self.createSomeComments()
+ pk = comments[0].pk
+ response = self.client.get("/approved/", data={"c":pk})
+ self.assertTemplateUsed(response, "comments/approved.html")
+
+class AdminActionsTests(CommentTestCase):
+ urls = "regressiontests.comment_tests.urls_admin"
+
+ def setUp(self):
+ super(AdminActionsTests, self).setUp()
+
+ # Make "normaluser" a moderator
+ u = User.objects.get(username="normaluser")
+ u.is_staff = True
+ perms = Permission.objects.filter(
+ content_type__app_label = 'comments',
+ codename__endswith = 'comment'
+ )
+ for perm in perms:
+ u.user_permissions.add(perm)
+ u.save()
+
+ def testActionsNonModerator(self):
+ comments = self.createSomeComments()
+ self.client.login(username="normaluser", password="normaluser")
+ response = self.client.get("/admin/comments/comment/")
+ self.assertNotContains(response, "approve_comments")
+
+ def testActionsModerator(self):
+ comments = self.createSomeComments()
+ makeModerator("normaluser")
+ self.client.login(username="normaluser", password="normaluser")
+ response = self.client.get("/admin/comments/comment/")
+ self.assertContains(response, "approve_comments")
+
+ def testActionsDisabledDelete(self):
+ "Tests a CommentAdmin where 'delete_selected' has been disabled."
+ comments = self.createSomeComments()
+ self.client.login(username="normaluser", password="normaluser")
+ response = self.client.get('/admin2/comments/comment/')
+ self.assertEqual(response.status_code, 200)
+ self.assertNotContains(response, '<option value="delete_selected">')
+
+ def performActionAndCheckMessage(self, action, action_params, expected_message):
+ response = self.client.post('/admin/comments/comment/',
+ data={'_selected_action': action_params,
+ 'action': action,
+ 'index': 0},
+ follow=True)
+ self.assertContains(response, expected_message)
+
+ def testActionsMessageTranslations(self):
+ c1, c2, c3, c4 = self.createSomeComments()
+ one_comment = c1.pk
+ many_comments = [c2.pk, c3.pk, c4.pk]
+ makeModerator("normaluser")
+ self.client.login(username="normaluser", password="normaluser")
+ with translation.override('en'):
+ #Test approving
+ self.performActionAndCheckMessage('approve_comments', one_comment, '1 comment was successfully approved')
+ self.performActionAndCheckMessage('approve_comments', many_comments, '3 comments were successfully approved')
+ #Test flagging
+ self.performActionAndCheckMessage('flag_comments', one_comment, '1 comment was successfully flagged')
+ self.performActionAndCheckMessage('flag_comments', many_comments, '3 comments were successfully flagged')
+ #Test removing
+ self.performActionAndCheckMessage('remove_comments', one_comment, '1 comment was successfully removed')
+ self.performActionAndCheckMessage('remove_comments', many_comments, '3 comments were successfully removed')
diff --git a/tests/comment_tests/tests/templatetag_tests.py b/tests/comment_tests/tests/templatetag_tests.py
new file mode 100644
index 0000000000..62b3fb35db
--- /dev/null
+++ b/tests/comment_tests/tests/templatetag_tests.py
@@ -0,0 +1,171 @@
+from __future__ import absolute_import
+
+from django.contrib.comments.forms import CommentForm
+from django.contrib.comments.models import Comment
+from django.contrib.contenttypes.models import ContentType
+from django.template import Template, Context
+
+from ..models import Article, Author
+from . import CommentTestCase
+
+
+class CommentTemplateTagTests(CommentTestCase):
+
+ def render(self, t, **c):
+ ctx = Context(c)
+ out = Template(t).render(ctx)
+ return ctx, out
+
+ def testCommentFormTarget(self):
+ ctx, out = self.render("{% load comments %}{% comment_form_target %}")
+ self.assertEqual(out, "/post/")
+
+ def testGetCommentForm(self, tag=None):
+ t = "{% load comments %}" + (tag or "{% get_comment_form for comment_tests.article a.id as form %}")
+ ctx, out = self.render(t, a=Article.objects.get(pk=1))
+ self.assertEqual(out, "")
+ self.assertTrue(isinstance(ctx["form"], CommentForm))
+
+ def testGetCommentFormFromLiteral(self):
+ self.testGetCommentForm("{% get_comment_form for comment_tests.article 1 as form %}")
+
+ def testGetCommentFormFromObject(self):
+ self.testGetCommentForm("{% get_comment_form for a as form %}")
+
+ def testRenderCommentForm(self, tag=None):
+ t = "{% load comments %}" + (tag or "{% render_comment_form for comment_tests.article a.id %}")
+ ctx, out = self.render(t, a=Article.objects.get(pk=1))
+ self.assertTrue(out.strip().startswith("<form action="))
+ self.assertTrue(out.strip().endswith("</form>"))
+
+ def testRenderCommentFormFromLiteral(self):
+ self.testRenderCommentForm("{% render_comment_form for comment_tests.article 1 %}")
+
+ def testRenderCommentFormFromObject(self):
+ self.testRenderCommentForm("{% render_comment_form for a %}")
+
+ def testRenderCommentFormFromObjectWithQueryCount(self):
+ with self.assertNumQueries(1):
+ self.testRenderCommentFormFromObject()
+
+ def verifyGetCommentCount(self, tag=None):
+ t = "{% load comments %}" + (tag or "{% get_comment_count for comment_tests.article a.id as cc %}") + "{{ cc }}"
+ ctx, out = self.render(t, a=Article.objects.get(pk=1))
+ self.assertEqual(out, "2")
+
+ def testGetCommentCount(self):
+ self.createSomeComments()
+ self.verifyGetCommentCount("{% get_comment_count for comment_tests.article a.id as cc %}")
+
+ def testGetCommentCountFromLiteral(self):
+ self.createSomeComments()
+ self.verifyGetCommentCount("{% get_comment_count for comment_tests.article 1 as cc %}")
+
+ def testGetCommentCountFromObject(self):
+ self.createSomeComments()
+ self.verifyGetCommentCount("{% get_comment_count for a as cc %}")
+
+ def verifyGetCommentList(self, tag=None):
+ c1, c2, c3, c4 = Comment.objects.all()[:4]
+ t = "{% load comments %}" + (tag or "{% get_comment_list for comment_tests.author a.id as cl %}")
+ ctx, out = self.render(t, a=Author.objects.get(pk=1))
+ self.assertEqual(out, "")
+ self.assertEqual(list(ctx["cl"]), [c2])
+
+ def testGetCommentList(self):
+ self.createSomeComments()
+ self.verifyGetCommentList("{% get_comment_list for comment_tests.author a.id as cl %}")
+
+ def testGetCommentListFromLiteral(self):
+ self.createSomeComments()
+ self.verifyGetCommentList("{% get_comment_list for comment_tests.author 1 as cl %}")
+
+ def testGetCommentListFromObject(self):
+ self.createSomeComments()
+ self.verifyGetCommentList("{% get_comment_list for a as cl %}")
+
+ def testGetCommentPermalink(self):
+ c1, c2, c3, c4 = self.createSomeComments()
+ t = "{% load comments %}{% get_comment_list for comment_tests.author author.id as cl %}"
+ t += "{% get_comment_permalink cl.0 %}"
+ ct = ContentType.objects.get_for_model(Author)
+ author = Author.objects.get(pk=1)
+ ctx, out = self.render(t, author=author)
+ self.assertEqual(out, "/cr/%s/%s/#c%s" % (ct.id, author.id, c2.id))
+
+ def testGetCommentPermalinkFormatted(self):
+ c1, c2, c3, c4 = self.createSomeComments()
+ t = "{% load comments %}{% get_comment_list for comment_tests.author author.id as cl %}"
+ t += "{% get_comment_permalink cl.0 '#c%(id)s-by-%(user_name)s' %}"
+ ct = ContentType.objects.get_for_model(Author)
+ author = Author.objects.get(pk=1)
+ ctx, out = self.render(t, author=author)
+ self.assertEqual(out, "/cr/%s/%s/#c%s-by-Joe Somebody" % (ct.id, author.id, c2.id))
+
+ def testRenderCommentList(self, tag=None):
+ t = "{% load comments %}" + (tag or "{% render_comment_list for comment_tests.article a.id %}")
+ ctx, out = self.render(t, a=Article.objects.get(pk=1))
+ self.assertTrue(out.strip().startswith("<dl id=\"comments\">"))
+ self.assertTrue(out.strip().endswith("</dl>"))
+
+ def testRenderCommentListFromLiteral(self):
+ self.testRenderCommentList("{% render_comment_list for comment_tests.article 1 %}")
+
+ def testRenderCommentListFromObject(self):
+ self.testRenderCommentList("{% render_comment_list for a %}")
+
+ def testNumberQueries(self):
+ """
+ Ensure that the template tags use cached content types to reduce the
+ number of DB queries.
+ Refs #16042.
+ """
+
+ self.createSomeComments()
+
+ # {% render_comment_list %} -----------------
+
+ # Clear CT cache
+ ContentType.objects.clear_cache()
+ with self.assertNumQueries(4):
+ self.testRenderCommentListFromObject()
+
+ # CT's should be cached
+ with self.assertNumQueries(3):
+ self.testRenderCommentListFromObject()
+
+ # {% get_comment_list %} --------------------
+
+ ContentType.objects.clear_cache()
+ with self.assertNumQueries(4):
+ self.verifyGetCommentList()
+
+ with self.assertNumQueries(3):
+ self.verifyGetCommentList()
+
+ # {% render_comment_form %} -----------------
+
+ ContentType.objects.clear_cache()
+ with self.assertNumQueries(3):
+ self.testRenderCommentForm()
+
+ with self.assertNumQueries(2):
+ self.testRenderCommentForm()
+
+ # {% get_comment_form %} --------------------
+
+ ContentType.objects.clear_cache()
+ with self.assertNumQueries(3):
+ self.testGetCommentForm()
+
+ with self.assertNumQueries(2):
+ self.testGetCommentForm()
+
+ # {% get_comment_count %} -------------------
+
+ ContentType.objects.clear_cache()
+ with self.assertNumQueries(3):
+ self.verifyGetCommentCount()
+
+ with self.assertNumQueries(2):
+ self.verifyGetCommentCount()
diff --git a/tests/comment_tests/urls.py b/tests/comment_tests/urls.py
new file mode 100644
index 0000000000..0a7e8b5fdf
--- /dev/null
+++ b/tests/comment_tests/urls.py
@@ -0,0 +1,23 @@
+from __future__ import absolute_import
+
+from django.conf.urls import patterns, url
+from django.contrib.comments.feeds import LatestCommentFeed
+
+from .custom_comments import views
+
+
+feeds = {
+ 'comments': LatestCommentFeed,
+}
+
+urlpatterns = patterns('',
+ url(r'^post/$', views.custom_submit_comment),
+ url(r'^flag/(\d+)/$', views.custom_flag_comment),
+ url(r'^delete/(\d+)/$', views.custom_delete_comment),
+ url(r'^approve/(\d+)/$', views.custom_approve_comment),
+ url(r'^cr/(\d+)/(.+)/$', 'django.contrib.contenttypes.views.shortcut', name='comments-url-redirect'),
+)
+
+urlpatterns += patterns('',
+ (r'^rss/comments/$', LatestCommentFeed()),
+)
diff --git a/tests/comment_tests/urls_admin.py b/tests/comment_tests/urls_admin.py
new file mode 100644
index 0000000000..79a1b75451
--- /dev/null
+++ b/tests/comment_tests/urls_admin.py
@@ -0,0 +1,19 @@
+from django.conf.urls import patterns, include
+from django.contrib import admin
+from django.contrib.comments.admin import CommentsAdmin
+from django.contrib.comments.models import Comment
+
+# Make a new AdminSite to avoid picking up the deliberately broken admin
+# modules in other tests.
+admin_site = admin.AdminSite()
+admin_site.register(Comment, CommentsAdmin)
+
+# To demonstrate proper functionality even when ``delete_selected`` is removed.
+admin_site2 = admin.AdminSite()
+admin_site2.disable_action('delete_selected')
+admin_site2.register(Comment, CommentsAdmin)
+
+urlpatterns = patterns('',
+ (r'^admin/', include(admin_site.urls)),
+ (r'^admin2/', include(admin_site2.urls)),
+)
diff --git a/tests/comment_tests/urls_default.py b/tests/comment_tests/urls_default.py
new file mode 100644
index 0000000000..e204f9ebcb
--- /dev/null
+++ b/tests/comment_tests/urls_default.py
@@ -0,0 +1,9 @@
+from django.conf.urls import patterns, include
+
+urlpatterns = patterns('',
+ (r'^', include('django.contrib.comments.urls')),
+
+ # Provide the auth system login and logout views
+ (r'^accounts/login/$', 'django.contrib.auth.views.login', {'template_name': 'login.html'}),
+ (r'^accounts/logout/$', 'django.contrib.auth.views.logout'),
+)