From fa7f2eab774f4b2366c8cf8302c2c53697518732 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Dlouh=C3=BD?= Date: Tue, 19 Oct 2021 17:36:11 +0200 Subject: [PATCH] allow to make comments for non-abstract models by for_concrete_model attribute --- django_comments/forms.py | 22 +++++++++++--- tests/testapp/models.py | 12 ++++++++ tests/testapp/tests/test_comment_form.py | 37 +++++++++++++++++++++++- 3 files changed, 66 insertions(+), 5 deletions(-) diff --git a/django_comments/forms.py b/django_comments/forms.py index d9a9782..1452b20 100644 --- a/django_comments/forms.py +++ b/django_comments/forms.py @@ -105,7 +105,7 @@ class CommentDetailsForm(CommentSecurityForm): comment = forms.CharField(label=_('Comment'), widget=forms.Textarea, max_length=COMMENT_MAX_LENGTH) - def get_comment_object(self, site_id=None): + def get_comment_object(self, site_id=None, for_concrete_model=True): """ Return a new (unsaved) comment object based on the information in this form. Assumes that the form is already validated and will throw a @@ -113,12 +113,23 @@ def get_comment_object(self, site_id=None): Does not set any of the fields that would come from a Request object (i.e. ``user`` or ``ip_address``). + + :param for_concrete_model: To change behavior of a model specifically for `django-comments` app like + overriding `_get_pk_val()` to use non-pk UUID field as key for comments in URL. + + Django by default returns non-proxy ancestor when determining content type of proxy model. + With `COMMENTS_FOR_CONCRETE_MODEL=False` the `django-comments` app will pass `for_concrete_model=False` + argument when determining content type via `ContentType.get_for_model()` method. """ if not self.is_valid(): raise ValueError("get_comment_object may only be called on valid forms") CommentModel = self.get_comment_model() - new = CommentModel(**self.get_comment_create_data(site_id=site_id)) + try: + create_data = self.get_comment_create_data(site_id=site_id, for_concrete_model=for_concrete_model) + except TypeError: # Fallback for overrides that doesn't count with more attributes + create_data = self.get_comment_create_data(site_id=site_id) + new = CommentModel(**create_data) new = self.check_for_duplicate_comment(new) return new @@ -131,14 +142,17 @@ def get_comment_model(self): """ return get_model() - def get_comment_create_data(self, site_id=None): + def get_comment_create_data(self, site_id=None, for_concrete_model=True): """ Returns the dict of data to be used to create a comment. Subclasses in custom comment apps that override get_comment_model can override this method to add extra fields onto a custom comment model. """ return dict( - content_type=ContentType.objects.get_for_model(self.target_object), + content_type=ContentType.objects.get_for_model( + self.target_object, + for_concrete_model=for_concrete_model, + ), object_pk=force_str(self.target_object._get_pk_val()), user_name=self.cleaned_data["name"], user_email=self.cleaned_data["email"], diff --git a/tests/testapp/models.py b/tests/testapp/models.py index 76ee39f..4b8a733 100644 --- a/tests/testapp/models.py +++ b/tests/testapp/models.py @@ -4,6 +4,7 @@ """ from django.db import models +import uuid class Author(models.Model): @@ -15,6 +16,7 @@ def __str__(self): class Article(models.Model): + uuid = models.UUIDField(editable=False, null=True) author = models.ForeignKey(Author, on_delete=models.CASCADE) headline = models.CharField(max_length=100) @@ -22,6 +24,16 @@ def __str__(self): return self.headline +class UUIDArticle(Article): + """ Override _get_pk_val to use UUID as PK """ + + def _get_pk_val(self, meta=None): + return self.uuid + + class Meta: + proxy = True + + class Entry(models.Model): title = models.CharField(max_length=250) body = models.TextField() diff --git a/tests/testapp/tests/test_comment_form.py b/tests/testapp/tests/test_comment_form.py index e5ce656..b0f9ccd 100644 --- a/tests/testapp/tests/test_comment_form.py +++ b/tests/testapp/tests/test_comment_form.py @@ -1,13 +1,15 @@ import time from django.conf import settings +from django.contrib.contenttypes.models import ContentType from django.contrib.sites.models import Site from django_comments.forms import CommentForm from django_comments.models import Comment from . import CommentTestCase -from testapp.models import Article +from testapp.models import UUIDArticle, Article +from django.test.utils import override_settings class CommentFormTests(CommentTestCase): @@ -75,6 +77,39 @@ def testGetCommentObject(self): c = f.get_comment_object(site_id=self.site_2.id) self.assertEqual(c.site_id, self.site_2.id) + def testGetCommentCreateData(self): + """ + Test that get_comment_create_data() returns + content_type for Article even if the proxy model UUIDArticle + is set to CommentForm. + """ + a = UUIDArticle.objects.get(pk=1) + d = self.getValidData(a) + d["comment"] = "testGetCommentObject with a site" + f = CommentForm(UUIDArticle.objects.get(pk=1), data=d) + self.assertTrue(f.is_valid()) + c = f.get_comment_create_data(site_id=self.site_2.id) + self.assertEqual(c["content_type"], ContentType.objects.get_for_model(Article, for_concrete_model=False)) + + o = f.get_comment_object(site_id=self.site_2.id) + self.assertEqual(o.content_type, ContentType.objects.get_for_model(Article, for_concrete_model=False)) + + def testGetCommentCreateDataConcreteModel(self): + """ + Test that get_comment_create_data() returns + content_type for UUIDArticle if COMMENTS_FOR_CONCRETE_MODEL is False. + """ + a = UUIDArticle.objects.get(pk=1) + d = self.getValidData(a) + d["comment"] = "testGetCommentObject with a site" + f = CommentForm(UUIDArticle.objects.get(pk=1), data=d) + self.assertTrue(f.is_valid()) + c = f.get_comment_create_data(site_id=self.site_2.id, for_concrete_model=False) + self.assertEqual(c["content_type"], ContentType.objects.get_for_model(UUIDArticle, for_concrete_model=False)) + + o = f.get_comment_object(site_id=self.site_2.id, for_concrete_model=False) + self.assertEqual(o.content_type, ContentType.objects.get_for_model(UUIDArticle, for_concrete_model=False)) + def testProfanities(self): """Test COMMENTS_ALLOW_PROFANITIES and PROFANITIES_LIST settings""" a = Article.objects.get(pk=1)