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 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 2 from django.db import models
7   -from django.utils.translation import ugettext_lazy as _
8 3 from django.utils import timezone
9 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 12 class Comment(CommentAbstractModel):
... ...
... ... @@ -61,16 +61,16 @@ the ``my_comment_app`` directory::
61 61 In the ``models.py`` we'll define a ``CommentWithTitle`` model::
62 62
63 63 from django.db import models
64   - from django_comments.models import CommentAbstractModel
  64 + from django_comments.abstracts import CommentAbstractModel
65 65
66 66 class CommentWithTitle(CommentAbstractModel):
67 67 title = models.CharField(max_length=300)
68 68
69 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 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 74 ``BaseCommentAbstractModel``.
75 75
76 76 Next, we'll define a custom comment form in ``forms.py``. This is a little more
... ... @@ -137,7 +137,7 @@ however.
137 137
138 138 Return the :class:`~django.db.models.Model` class to use for comments. This
139 139 model should inherit from
140   - ``django_comments.models.BaseCommentAbstractModel``, which
  140 + ``django_comments.abstracts.BaseCommentAbstractModel``, which
141 141 defines necessary core fields.
142 142
143 143 The default implementation returns
... ...
Please register or login to post a comment