Commit 10fac3f08a6704eab0e9010d25036bc56b41c5eb

Authored by Claude Paroz
1 parent af99bae5

Isolated abstract models outside of models.py

Fixes #83. Thanks Julen Ruiz Aizpuru for the report and the review.
  1 +from __future__ import unicode_literals
  2 +
  3 +from django.conf import settings
  4 +from django.contrib.contenttypes.fields import GenericForeignKey
  5 +from django.contrib.contenttypes.models import ContentType
  6 +from django.contrib.sites.models import Site
  7 +from django.core import urlresolvers
  8 +from django.db import models
  9 +from django.utils import timezone
  10 +from django.utils.encoding import python_2_unicode_compatible
  11 +from django.utils.translation import ugettext_lazy as _
  12 +
  13 +from .managers import CommentManager
  14 +
  15 +COMMENT_MAX_LENGTH = getattr(settings, 'COMMENT_MAX_LENGTH', 3000)
  16 +
  17 +
  18 +class BaseCommentAbstractModel(models.Model):
  19 + """
  20 + An abstract base class that any custom comment models probably should
  21 + subclass.
  22 + """
  23 +
  24 + # Content-object field
  25 + content_type = models.ForeignKey(ContentType,
  26 + verbose_name=_('content type'),
  27 + related_name="content_type_set_for_%(class)s",
  28 + on_delete=models.CASCADE)
  29 + object_pk = models.TextField(_('object ID'))
  30 + content_object = GenericForeignKey(ct_field="content_type", fk_field="object_pk")
  31 +
  32 + # Metadata about the comment
  33 + site = models.ForeignKey(Site, on_delete=models.CASCADE)
  34 +
  35 + class Meta:
  36 + abstract = True
  37 +
  38 + def get_content_object_url(self):
  39 + """
  40 + Get a URL suitable for redirecting to the content object.
  41 + """
  42 + return urlresolvers.reverse(
  43 + "comments-url-redirect",
  44 + args=(self.content_type_id, self.object_pk)
  45 + )
  46 +
  47 +
  48 +@python_2_unicode_compatible
  49 +class CommentAbstractModel(BaseCommentAbstractModel):
  50 + """
  51 + A user comment about some object.
  52 + """
  53 +
  54 + # Who posted this comment? If ``user`` is set then it was an authenticated
  55 + # user; otherwise at least user_name should have been set and the comment
  56 + # was posted by a non-authenticated user.
  57 + user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('user'),
  58 + blank=True, null=True, related_name="%(class)s_comments",
  59 + on_delete=models.SET_NULL)
  60 + user_name = models.CharField(_("user's name"), max_length=50, blank=True)
  61 + # Explicit `max_length` to apply both to Django 1.7 and 1.8+.
  62 + user_email = models.EmailField(_("user's email address"), max_length=254,
  63 + blank=True)
  64 + user_url = models.URLField(_("user's URL"), blank=True)
  65 +
  66 + comment = models.TextField(_('comment'), max_length=COMMENT_MAX_LENGTH)
  67 +
  68 + # Metadata about the comment
  69 + submit_date = models.DateTimeField(_('date/time submitted'), default=None, db_index=True)
  70 + ip_address = models.GenericIPAddressField(_('IP address'), unpack_ipv4=True, blank=True, null=True)
  71 + is_public = models.BooleanField(_('is public'), default=True,
  72 + help_text=_('Uncheck this box to make the comment effectively '
  73 + 'disappear from the site.'))
  74 + is_removed = models.BooleanField(_('is removed'), default=False,
  75 + help_text=_('Check this box if the comment is inappropriate. '
  76 + 'A "This comment has been removed" message will '
  77 + 'be displayed instead.'))
  78 +
  79 + # Manager
  80 + objects = CommentManager()
  81 +
  82 + class Meta:
  83 + abstract = True
  84 + ordering = ('submit_date',)
  85 + permissions = [("can_moderate", "Can moderate comments")]
  86 + verbose_name = _('comment')
  87 + verbose_name_plural = _('comments')
  88 +
  89 + def __str__(self):
  90 + return "%s: %s..." % (self.name, self.comment[:50])
  91 +
  92 + def save(self, *args, **kwargs):
  93 + if self.submit_date is None:
  94 + self.submit_date = timezone.now()
  95 + super(CommentAbstractModel, self).save(*args, **kwargs)
  96 +
  97 + def _get_userinfo(self):
  98 + """
  99 + Get a dictionary that pulls together information about the poster
  100 + safely for both authenticated and non-authenticated comments.
  101 +
  102 + This dict will have ``name``, ``email``, and ``url`` fields.
  103 + """
  104 + if not hasattr(self, "_userinfo"):
  105 + userinfo = {
  106 + "name": self.user_name,
  107 + "email": self.user_email,
  108 + "url": self.user_url
  109 + }
  110 + if self.user_id:
  111 + u = self.user
  112 + if u.email:
  113 + userinfo["email"] = u.email
  114 +
  115 + # If the user has a full name, use that for the user name.
  116 + # However, a given user_name overrides the raw user.username,
  117 + # so only use that if this comment has no associated name.
  118 + if u.get_full_name():
  119 + userinfo["name"] = self.user.get_full_name()
  120 + elif not self.user_name:
  121 + userinfo["name"] = u.get_username()
  122 + self._userinfo = userinfo
  123 + return self._userinfo
  124 +
  125 + userinfo = property(_get_userinfo, doc=_get_userinfo.__doc__)
  126 +
  127 + def _get_name(self):
  128 + return self.userinfo["name"]
  129 +
  130 + def _set_name(self, val):
  131 + if self.user_id:
  132 + raise AttributeError(_("This comment was posted by an authenticated "
  133 + "user and thus the name is read-only."))
  134 + self.user_name = val
  135 +
  136 + name = property(_get_name, _set_name, doc="The name of the user who posted this comment")
  137 +
  138 + def _get_email(self):
  139 + return self.userinfo["email"]
  140 +
  141 + def _set_email(self, val):
  142 + if self.user_id:
  143 + raise AttributeError(_("This comment was posted by an authenticated "
  144 + "user and thus the email is read-only."))
  145 + self.user_email = val
  146 +
  147 + email = property(_get_email, _set_email, doc="The email of the user who posted this comment")
  148 +
  149 + def _get_url(self):
  150 + return self.userinfo["url"]
  151 +
  152 + def _set_url(self, val):
  153 + self.user_url = val
  154 +
  155 + url = property(_get_url, _set_url, doc="The URL given by the user who posted this comment")
  156 +
  157 + def get_absolute_url(self, anchor_pattern="#c%(id)s"):
  158 + return self.get_content_object_url() + (anchor_pattern % self.__dict__)
  159 +
  160 + def get_as_text(self):
  161 + """
  162 + Return this comment as plain text. Useful for emails.
  163 + """
  164 + d = {
  165 + 'user': self.user or self.name,
  166 + 'date': self.submit_date,
  167 + 'comment': self.comment,
  168 + 'domain': self.site.domain,
  169 + 'url': self.get_absolute_url()
  170 + }
  171 + return _('Posted by %(user)s at %(date)s\n\n%(comment)s\n\nhttp://%(domain)s%(url)s') % d
1 from django.conf import settings 1 from django.conf import settings
2 -from django.contrib.contenttypes.fields import GenericForeignKey  
3 -from django.contrib.contenttypes.models import ContentType  
4 -from django.contrib.sites.models import Site  
5 -from django.core import urlresolvers  
6 from django.db import models 2 from django.db import models
7 -from django.utils.translation import ugettext_lazy as _  
8 from django.utils import timezone 3 from django.utils import timezone
9 from django.utils.encoding import python_2_unicode_compatible 4 from django.utils.encoding import python_2_unicode_compatible
  5 +from django.utils.translation import ugettext_lazy as _
10 6
11 -from django_comments.managers import CommentManager  
12 -  
13 -COMMENT_MAX_LENGTH = getattr(settings, 'COMMENT_MAX_LENGTH', 3000)  
14 -  
15 -  
16 -class BaseCommentAbstractModel(models.Model):  
17 - """  
18 - An abstract base class that any custom comment models probably should  
19 - subclass.  
20 - """  
21 -  
22 - # Content-object field  
23 - content_type = models.ForeignKey(ContentType,  
24 - verbose_name=_('content type'),  
25 - related_name="content_type_set_for_%(class)s",  
26 - on_delete=models.CASCADE)  
27 - object_pk = models.TextField(_('object ID'))  
28 - content_object = GenericForeignKey(ct_field="content_type", fk_field="object_pk")  
29 -  
30 - # Metadata about the comment  
31 - site = models.ForeignKey(Site, on_delete=models.CASCADE)  
32 -  
33 - class Meta:  
34 - abstract = True  
35 -  
36 - def get_content_object_url(self):  
37 - """  
38 - Get a URL suitable for redirecting to the content object.  
39 - """  
40 - return urlresolvers.reverse(  
41 - "comments-url-redirect",  
42 - args=(self.content_type_id, self.object_pk)  
43 - )  
44 -  
45 -  
46 -@python_2_unicode_compatible  
47 -class CommentAbstractModel(BaseCommentAbstractModel):  
48 - """  
49 - A user comment about some object.  
50 - """  
51 -  
52 - # Who posted this comment? If ``user`` is set then it was an authenticated  
53 - # user; otherwise at least user_name should have been set and the comment  
54 - # was posted by a non-authenticated user.  
55 - user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('user'),  
56 - blank=True, null=True, related_name="%(class)s_comments",  
57 - on_delete=models.SET_NULL)  
58 - user_name = models.CharField(_("user's name"), max_length=50, blank=True)  
59 - # Explicit `max_length` to apply both to Django 1.7 and 1.8+.  
60 - user_email = models.EmailField(_("user's email address"), max_length=254,  
61 - blank=True)  
62 - user_url = models.URLField(_("user's URL"), blank=True)  
63 -  
64 - comment = models.TextField(_('comment'), max_length=COMMENT_MAX_LENGTH)  
65 -  
66 - # Metadata about the comment  
67 - submit_date = models.DateTimeField(_('date/time submitted'), default=None, db_index=True)  
68 - ip_address = models.GenericIPAddressField(_('IP address'), unpack_ipv4=True, blank=True, null=True)  
69 - is_public = models.BooleanField(_('is public'), default=True,  
70 - help_text=_('Uncheck this box to make the comment effectively '  
71 - 'disappear from the site.'))  
72 - is_removed = models.BooleanField(_('is removed'), default=False,  
73 - help_text=_('Check this box if the comment is inappropriate. '  
74 - 'A "This comment has been removed" message will '  
75 - 'be displayed instead.'))  
76 -  
77 - # Manager  
78 - objects = CommentManager()  
79 -  
80 - class Meta:  
81 - abstract = True  
82 - ordering = ('submit_date',)  
83 - permissions = [("can_moderate", "Can moderate comments")]  
84 - verbose_name = _('comment')  
85 - verbose_name_plural = _('comments')  
86 -  
87 - def __str__(self):  
88 - return "%s: %s..." % (self.name, self.comment[:50])  
89 -  
90 - def save(self, *args, **kwargs):  
91 - if self.submit_date is None:  
92 - self.submit_date = timezone.now()  
93 - super(CommentAbstractModel, self).save(*args, **kwargs)  
94 -  
95 - def _get_userinfo(self):  
96 - """  
97 - Get a dictionary that pulls together information about the poster  
98 - safely for both authenticated and non-authenticated comments.  
99 -  
100 - This dict will have ``name``, ``email``, and ``url`` fields.  
101 - """  
102 - if not hasattr(self, "_userinfo"):  
103 - userinfo = {  
104 - "name": self.user_name,  
105 - "email": self.user_email,  
106 - "url": self.user_url  
107 - }  
108 - if self.user_id:  
109 - u = self.user  
110 - if u.email:  
111 - userinfo["email"] = u.email  
112 -  
113 - # If the user has a full name, use that for the user name.  
114 - # However, a given user_name overrides the raw user.username,  
115 - # so only use that if this comment has no associated name.  
116 - if u.get_full_name():  
117 - userinfo["name"] = self.user.get_full_name()  
118 - elif not self.user_name:  
119 - userinfo["name"] = u.get_username()  
120 - self._userinfo = userinfo  
121 - return self._userinfo  
122 -  
123 - userinfo = property(_get_userinfo, doc=_get_userinfo.__doc__)  
124 -  
125 - def _get_name(self):  
126 - return self.userinfo["name"]  
127 -  
128 - def _set_name(self, val):  
129 - if self.user_id:  
130 - raise AttributeError(_("This comment was posted by an authenticated "  
131 - "user and thus the name is read-only."))  
132 - self.user_name = val  
133 -  
134 - name = property(_get_name, _set_name, doc="The name of the user who posted this comment")  
135 -  
136 - def _get_email(self):  
137 - return self.userinfo["email"]  
138 -  
139 - def _set_email(self, val):  
140 - if self.user_id:  
141 - raise AttributeError(_("This comment was posted by an authenticated "  
142 - "user and thus the email is read-only."))  
143 - self.user_email = val  
144 -  
145 - email = property(_get_email, _set_email, doc="The email of the user who posted this comment")  
146 -  
147 - def _get_url(self):  
148 - return self.userinfo["url"]  
149 -  
150 - def _set_url(self, val):  
151 - self.user_url = val  
152 -  
153 - url = property(_get_url, _set_url, doc="The URL given by the user who posted this comment")  
154 -  
155 - def get_absolute_url(self, anchor_pattern="#c%(id)s"):  
156 - return self.get_content_object_url() + (anchor_pattern % self.__dict__)  
157 -  
158 - def get_as_text(self):  
159 - """  
160 - Return this comment as plain text. Useful for emails.  
161 - """  
162 - d = {  
163 - 'user': self.user or self.name,  
164 - 'date': self.submit_date,  
165 - 'comment': self.comment,  
166 - 'domain': self.site.domain,  
167 - 'url': self.get_absolute_url()  
168 - }  
169 - return _('Posted by %(user)s at %(date)s\n\n%(comment)s\n\nhttp://%(domain)s%(url)s') % d 7 +from .abstracts import (
  8 + COMMENT_MAX_LENGTH, BaseCommentAbstractModel, CommentAbstractModel,
  9 +)
170 10
171 11
172 class Comment(CommentAbstractModel): 12 class Comment(CommentAbstractModel):
@@ -61,16 +61,16 @@ the ``my_comment_app`` directory:: @@ -61,16 +61,16 @@ the ``my_comment_app`` directory::
61 In the ``models.py`` we'll define a ``CommentWithTitle`` model:: 61 In the ``models.py`` we'll define a ``CommentWithTitle`` model::
62 62
63 from django.db import models 63 from django.db import models
64 - from django_comments.models import CommentAbstractModel 64 + from django_comments.abstracts import CommentAbstractModel
65 65
66 class CommentWithTitle(CommentAbstractModel): 66 class CommentWithTitle(CommentAbstractModel):
67 title = models.CharField(max_length=300) 67 title = models.CharField(max_length=300)
68 68
69 Most custom comment models will subclass the 69 Most custom comment models will subclass the
70 -:class:`~django_comments.models.CommentAbstractModel` model. However, 70 +:class:`~django_comments.abstracts.CommentAbstractModel` model. However,
71 if you want to substantially remove or change the fields available in the 71 if you want to substantially remove or change the fields available in the
72 -:class:`~django_comments.models.CommentAbstractModel` model, but don't want to  
73 -rewrite the templates, you could try subclassing from 72 +:class:`~django_comments.abstracts.CommentAbstractModel` model, but don't want
  73 +to rewrite the templates, you could try subclassing from
74 ``BaseCommentAbstractModel``. 74 ``BaseCommentAbstractModel``.
75 75
76 Next, we'll define a custom comment form in ``forms.py``. This is a little more 76 Next, we'll define a custom comment form in ``forms.py``. This is a little more
@@ -137,7 +137,7 @@ however. @@ -137,7 +137,7 @@ however.
137 137
138 Return the :class:`~django.db.models.Model` class to use for comments. This 138 Return the :class:`~django.db.models.Model` class to use for comments. This
139 model should inherit from 139 model should inherit from
140 - ``django_comments.models.BaseCommentAbstractModel``, which 140 + ``django_comments.abstracts.BaseCommentAbstractModel``, which
141 defines necessary core fields. 141 defines necessary core fields.
142 142
143 The default implementation returns 143 The default implementation returns
Please register or login to post a comment