Skip to content

Commit

Permalink
Isolated abstract models outside of models.py
Browse files Browse the repository at this point in the history
Fixes #83. Thanks Julen Ruiz Aizpuru for the report and the review.
  • Loading branch information
claudep committed Apr 29, 2016
1 parent af99bae commit 10fac3f
Show file tree
Hide file tree
Showing 3 changed files with 180 additions and 169 deletions.
171 changes: 171 additions & 0 deletions django_comments/abstracts.py
@@ -0,0 +1,171 @@
from __future__ import unicode_literals

from django.conf import settings
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.contrib.sites.models import Site
from django.core import urlresolvers
from django.db import models
from django.utils import timezone
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _

from .managers import CommentManager

COMMENT_MAX_LENGTH = getattr(settings, 'COMMENT_MAX_LENGTH', 3000)


class BaseCommentAbstractModel(models.Model):
"""
An abstract base class that any custom comment models probably should
subclass.
"""

# Content-object field
content_type = models.ForeignKey(ContentType,
verbose_name=_('content type'),
related_name="content_type_set_for_%(class)s",
on_delete=models.CASCADE)
object_pk = models.TextField(_('object ID'))
content_object = GenericForeignKey(ct_field="content_type", fk_field="object_pk")

# Metadata about the comment
site = models.ForeignKey(Site, on_delete=models.CASCADE)

class Meta:
abstract = True

def get_content_object_url(self):
"""
Get a URL suitable for redirecting to the content object.
"""
return urlresolvers.reverse(
"comments-url-redirect",
args=(self.content_type_id, self.object_pk)
)


@python_2_unicode_compatible
class CommentAbstractModel(BaseCommentAbstractModel):
"""
A user comment about some object.
"""

# Who posted this comment? If ``user`` is set then it was an authenticated
# user; otherwise at least user_name should have been set and the comment
# was posted by a non-authenticated user.
user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('user'),
blank=True, null=True, related_name="%(class)s_comments",
on_delete=models.SET_NULL)
user_name = models.CharField(_("user's name"), max_length=50, blank=True)
# Explicit `max_length` to apply both to Django 1.7 and 1.8+.
user_email = models.EmailField(_("user's email address"), max_length=254,
blank=True)
user_url = models.URLField(_("user's URL"), blank=True)

comment = models.TextField(_('comment'), max_length=COMMENT_MAX_LENGTH)

# Metadata about the comment
submit_date = models.DateTimeField(_('date/time submitted'), default=None, db_index=True)
ip_address = models.GenericIPAddressField(_('IP address'), unpack_ipv4=True, blank=True, null=True)
is_public = models.BooleanField(_('is public'), default=True,
help_text=_('Uncheck this box to make the comment effectively '
'disappear from the site.'))
is_removed = models.BooleanField(_('is removed'), default=False,
help_text=_('Check this box if the comment is inappropriate. '
'A "This comment has been removed" message will '
'be displayed instead.'))

# Manager
objects = CommentManager()

class Meta:
abstract = True
ordering = ('submit_date',)
permissions = [("can_moderate", "Can moderate comments")]
verbose_name = _('comment')
verbose_name_plural = _('comments')

def __str__(self):
return "%s: %s..." % (self.name, self.comment[:50])

def save(self, *args, **kwargs):
if self.submit_date is None:
self.submit_date = timezone.now()
super(CommentAbstractModel, self).save(*args, **kwargs)

def _get_userinfo(self):
"""
Get a dictionary that pulls together information about the poster
safely for both authenticated and non-authenticated comments.
This dict will have ``name``, ``email``, and ``url`` fields.
"""
if not hasattr(self, "_userinfo"):
userinfo = {
"name": self.user_name,
"email": self.user_email,
"url": self.user_url
}
if self.user_id:
u = self.user
if u.email:
userinfo["email"] = u.email

# If the user has a full name, use that for the user name.
# However, a given user_name overrides the raw user.username,
# so only use that if this comment has no associated name.
if u.get_full_name():
userinfo["name"] = self.user.get_full_name()
elif not self.user_name:
userinfo["name"] = u.get_username()
self._userinfo = userinfo
return self._userinfo

userinfo = property(_get_userinfo, doc=_get_userinfo.__doc__)

def _get_name(self):
return self.userinfo["name"]

def _set_name(self, val):
if self.user_id:
raise AttributeError(_("This comment was posted by an authenticated "
"user and thus the name is read-only."))
self.user_name = val

name = property(_get_name, _set_name, doc="The name of the user who posted this comment")

def _get_email(self):
return self.userinfo["email"]

def _set_email(self, val):
if self.user_id:
raise AttributeError(_("This comment was posted by an authenticated "
"user and thus the email is read-only."))
self.user_email = val

email = property(_get_email, _set_email, doc="The email of the user who posted this comment")

def _get_url(self):
return self.userinfo["url"]

def _set_url(self, val):
self.user_url = val

url = property(_get_url, _set_url, doc="The URL given by the user who posted this comment")

def get_absolute_url(self, anchor_pattern="#c%(id)s"):
return self.get_content_object_url() + (anchor_pattern % self.__dict__)

def get_as_text(self):
"""
Return this comment as plain text. Useful for emails.
"""
d = {
'user': self.user or self.name,
'date': self.submit_date,
'comment': self.comment,
'domain': self.site.domain,
'url': self.get_absolute_url()
}
return _('Posted by %(user)s at %(date)s\n\n%(comment)s\n\nhttp://%(domain)s%(url)s') % d
168 changes: 4 additions & 164 deletions django_comments/models.py
@@ -1,172 +1,12 @@
from django.conf import settings
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.contrib.sites.models import Site
from django.core import urlresolvers
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.utils import timezone
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _

from django_comments.managers import CommentManager

COMMENT_MAX_LENGTH = getattr(settings, 'COMMENT_MAX_LENGTH', 3000)


class BaseCommentAbstractModel(models.Model):
"""
An abstract base class that any custom comment models probably should
subclass.
"""

# Content-object field
content_type = models.ForeignKey(ContentType,
verbose_name=_('content type'),
related_name="content_type_set_for_%(class)s",
on_delete=models.CASCADE)
object_pk = models.TextField(_('object ID'))
content_object = GenericForeignKey(ct_field="content_type", fk_field="object_pk")

# Metadata about the comment
site = models.ForeignKey(Site, on_delete=models.CASCADE)

class Meta:
abstract = True

def get_content_object_url(self):
"""
Get a URL suitable for redirecting to the content object.
"""
return urlresolvers.reverse(
"comments-url-redirect",
args=(self.content_type_id, self.object_pk)
)


@python_2_unicode_compatible
class CommentAbstractModel(BaseCommentAbstractModel):
"""
A user comment about some object.
"""

# Who posted this comment? If ``user`` is set then it was an authenticated
# user; otherwise at least user_name should have been set and the comment
# was posted by a non-authenticated user.
user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('user'),
blank=True, null=True, related_name="%(class)s_comments",
on_delete=models.SET_NULL)
user_name = models.CharField(_("user's name"), max_length=50, blank=True)
# Explicit `max_length` to apply both to Django 1.7 and 1.8+.
user_email = models.EmailField(_("user's email address"), max_length=254,
blank=True)
user_url = models.URLField(_("user's URL"), blank=True)

comment = models.TextField(_('comment'), max_length=COMMENT_MAX_LENGTH)

# Metadata about the comment
submit_date = models.DateTimeField(_('date/time submitted'), default=None, db_index=True)
ip_address = models.GenericIPAddressField(_('IP address'), unpack_ipv4=True, blank=True, null=True)
is_public = models.BooleanField(_('is public'), default=True,
help_text=_('Uncheck this box to make the comment effectively '
'disappear from the site.'))
is_removed = models.BooleanField(_('is removed'), default=False,
help_text=_('Check this box if the comment is inappropriate. '
'A "This comment has been removed" message will '
'be displayed instead.'))

# Manager
objects = CommentManager()

class Meta:
abstract = True
ordering = ('submit_date',)
permissions = [("can_moderate", "Can moderate comments")]
verbose_name = _('comment')
verbose_name_plural = _('comments')

def __str__(self):
return "%s: %s..." % (self.name, self.comment[:50])

def save(self, *args, **kwargs):
if self.submit_date is None:
self.submit_date = timezone.now()
super(CommentAbstractModel, self).save(*args, **kwargs)

def _get_userinfo(self):
"""
Get a dictionary that pulls together information about the poster
safely for both authenticated and non-authenticated comments.
This dict will have ``name``, ``email``, and ``url`` fields.
"""
if not hasattr(self, "_userinfo"):
userinfo = {
"name": self.user_name,
"email": self.user_email,
"url": self.user_url
}
if self.user_id:
u = self.user
if u.email:
userinfo["email"] = u.email

# If the user has a full name, use that for the user name.
# However, a given user_name overrides the raw user.username,
# so only use that if this comment has no associated name.
if u.get_full_name():
userinfo["name"] = self.user.get_full_name()
elif not self.user_name:
userinfo["name"] = u.get_username()
self._userinfo = userinfo
return self._userinfo

userinfo = property(_get_userinfo, doc=_get_userinfo.__doc__)

def _get_name(self):
return self.userinfo["name"]

def _set_name(self, val):
if self.user_id:
raise AttributeError(_("This comment was posted by an authenticated "
"user and thus the name is read-only."))
self.user_name = val

name = property(_get_name, _set_name, doc="The name of the user who posted this comment")

def _get_email(self):
return self.userinfo["email"]

def _set_email(self, val):
if self.user_id:
raise AttributeError(_("This comment was posted by an authenticated "
"user and thus the email is read-only."))
self.user_email = val

email = property(_get_email, _set_email, doc="The email of the user who posted this comment")

def _get_url(self):
return self.userinfo["url"]

def _set_url(self, val):
self.user_url = val

url = property(_get_url, _set_url, doc="The URL given by the user who posted this comment")

def get_absolute_url(self, anchor_pattern="#c%(id)s"):
return self.get_content_object_url() + (anchor_pattern % self.__dict__)

def get_as_text(self):
"""
Return this comment as plain text. Useful for emails.
"""
d = {
'user': self.user or self.name,
'date': self.submit_date,
'comment': self.comment,
'domain': self.site.domain,
'url': self.get_absolute_url()
}
return _('Posted by %(user)s at %(date)s\n\n%(comment)s\n\nhttp://%(domain)s%(url)s') % d
from .abstracts import (
COMMENT_MAX_LENGTH, BaseCommentAbstractModel, CommentAbstractModel,
)


class Comment(CommentAbstractModel):
Expand Down
10 changes: 5 additions & 5 deletions docs/custom.txt
Expand Up @@ -61,16 +61,16 @@ the ``my_comment_app`` directory::
In the ``models.py`` we'll define a ``CommentWithTitle`` model::

from django.db import models
from django_comments.models import CommentAbstractModel
from django_comments.abstracts import CommentAbstractModel

class CommentWithTitle(CommentAbstractModel):
title = models.CharField(max_length=300)

Most custom comment models will subclass the
:class:`~django_comments.models.CommentAbstractModel` model. However,
:class:`~django_comments.abstracts.CommentAbstractModel` model. However,
if you want to substantially remove or change the fields available in the
:class:`~django_comments.models.CommentAbstractModel` model, but don't want to
rewrite the templates, you could try subclassing from
:class:`~django_comments.abstracts.CommentAbstractModel` model, but don't want
to rewrite the templates, you could try subclassing from
``BaseCommentAbstractModel``.

Next, we'll define a custom comment form in ``forms.py``. This is a little more
Expand Down Expand Up @@ -137,7 +137,7 @@ however.

Return the :class:`~django.db.models.Model` class to use for comments. This
model should inherit from
``django_comments.models.BaseCommentAbstractModel``, which
``django_comments.abstracts.BaseCommentAbstractModel``, which
defines necessary core fields.

The default implementation returns
Expand Down

0 comments on commit 10fac3f

Please sign in to comment.