diff options
Diffstat (limited to 'tests/comment_tests')
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'), +) |
