app/django/contrib/comments/models.py
changeset 54 03e267d67478
child 323 ff1a9aa48cfd
equal deleted inserted replaced
53:57b4279d8c4e 54:03e267d67478
       
     1 import datetime
       
     2 
       
     3 from django.db import models
       
     4 from django.contrib.contenttypes.models import ContentType
       
     5 from django.contrib.sites.models import Site
       
     6 from django.contrib.auth.models import User
       
     7 from django.utils.translation import ugettext_lazy as _
       
     8 from django.conf import settings
       
     9 
       
    10 MIN_PHOTO_DIMENSION = 5
       
    11 MAX_PHOTO_DIMENSION = 1000
       
    12 
       
    13 # Option codes for comment-form hidden fields.
       
    14 PHOTOS_REQUIRED = 'pr'
       
    15 PHOTOS_OPTIONAL = 'pa'
       
    16 RATINGS_REQUIRED = 'rr'
       
    17 RATINGS_OPTIONAL = 'ra'
       
    18 IS_PUBLIC = 'ip'
       
    19 
       
    20 # What users get if they don't have any karma.
       
    21 DEFAULT_KARMA = 5
       
    22 KARMA_NEEDED_BEFORE_DISPLAYED = 3
       
    23 
       
    24 
       
    25 class CommentManager(models.Manager):
       
    26     def get_security_hash(self, options, photo_options, rating_options, target):
       
    27         """
       
    28         Returns the MD5 hash of the given options (a comma-separated string such as
       
    29         'pa,ra') and target (something like 'lcom.eventtimes:5157'). Used to
       
    30         validate that submitted form options have not been tampered-with.
       
    31         """
       
    32         import md5
       
    33         return md5.new(options + photo_options + rating_options + target + settings.SECRET_KEY).hexdigest()
       
    34 
       
    35     def get_rating_options(self, rating_string):
       
    36         """
       
    37         Given a rating_string, this returns a tuple of (rating_range, options).
       
    38         >>> s = "scale:1-10|First_category|Second_category"
       
    39         >>> Comment.objects.get_rating_options(s)
       
    40         ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], ['First category', 'Second category'])
       
    41         """
       
    42         rating_range, options = rating_string.split('|', 1)
       
    43         rating_range = range(int(rating_range[6:].split('-')[0]), int(rating_range[6:].split('-')[1])+1)
       
    44         choices = [c.replace('_', ' ') for c in options.split('|')]
       
    45         return rating_range, choices
       
    46 
       
    47     def get_list_with_karma(self, **kwargs):
       
    48         """
       
    49         Returns a list of Comment objects matching the given lookup terms, with
       
    50         _karma_total_good and _karma_total_bad filled.
       
    51         """
       
    52         extra_kwargs = {}
       
    53         extra_kwargs.setdefault('select', {})
       
    54         extra_kwargs['select']['_karma_total_good'] = 'SELECT COUNT(*) FROM comments_karmascore, comments_comment WHERE comments_karmascore.comment_id=comments_comment.id AND score=1'
       
    55         extra_kwargs['select']['_karma_total_bad'] = 'SELECT COUNT(*) FROM comments_karmascore, comments_comment WHERE comments_karmascore.comment_id=comments_comment.id AND score=-1'
       
    56         return self.filter(**kwargs).extra(**extra_kwargs)
       
    57 
       
    58     def user_is_moderator(self, user):
       
    59         if user.is_superuser:
       
    60             return True
       
    61         for g in user.groups.all():
       
    62             if g.id == settings.COMMENTS_MODERATORS_GROUP:
       
    63                 return True
       
    64         return False
       
    65 
       
    66 
       
    67 class Comment(models.Model):
       
    68     """A comment by a registered user."""
       
    69     user = models.ForeignKey(User, raw_id_admin=True)
       
    70     content_type = models.ForeignKey(ContentType)
       
    71     object_id = models.IntegerField(_('object ID'))
       
    72     headline = models.CharField(_('headline'), max_length=255, blank=True)
       
    73     comment = models.TextField(_('comment'), max_length=3000)
       
    74     rating1 = models.PositiveSmallIntegerField(_('rating #1'), blank=True, null=True)
       
    75     rating2 = models.PositiveSmallIntegerField(_('rating #2'), blank=True, null=True)
       
    76     rating3 = models.PositiveSmallIntegerField(_('rating #3'), blank=True, null=True)
       
    77     rating4 = models.PositiveSmallIntegerField(_('rating #4'), blank=True, null=True)
       
    78     rating5 = models.PositiveSmallIntegerField(_('rating #5'), blank=True, null=True)
       
    79     rating6 = models.PositiveSmallIntegerField(_('rating #6'), blank=True, null=True)
       
    80     rating7 = models.PositiveSmallIntegerField(_('rating #7'), blank=True, null=True)
       
    81     rating8 = models.PositiveSmallIntegerField(_('rating #8'), blank=True, null=True)
       
    82     # This field designates whether to use this row's ratings in aggregate
       
    83     # functions (summaries). We need this because people are allowed to post
       
    84     # multiple reviews on the same thing, but the system will only use the
       
    85     # latest one (with valid_rating=True) in tallying the reviews.
       
    86     valid_rating = models.BooleanField(_('is valid rating'))
       
    87     submit_date = models.DateTimeField(_('date/time submitted'), auto_now_add=True)
       
    88     is_public = models.BooleanField(_('is public'))
       
    89     ip_address = models.IPAddressField(_('IP address'), blank=True, null=True)
       
    90     is_removed = models.BooleanField(_('is removed'), help_text=_('Check this box if the comment is inappropriate. A "This comment has been removed" message will be displayed instead.'))
       
    91     site = models.ForeignKey(Site)
       
    92     objects = CommentManager()
       
    93 
       
    94     class Meta:
       
    95         verbose_name = _('comment')
       
    96         verbose_name_plural = _('comments')
       
    97         ordering = ('-submit_date',)
       
    98 
       
    99     class Admin:
       
   100         fields = (
       
   101             (None, {'fields': ('content_type', 'object_id', 'site')}),
       
   102             ('Content', {'fields': ('user', 'headline', 'comment')}),
       
   103             ('Ratings', {'fields': ('rating1', 'rating2', 'rating3', 'rating4', 'rating5', 'rating6', 'rating7', 'rating8', 'valid_rating')}),
       
   104             ('Meta', {'fields': ('is_public', 'is_removed', 'ip_address')}),
       
   105         )
       
   106         list_display = ('user', 'submit_date', 'content_type', 'get_content_object')
       
   107         list_filter = ('submit_date',)
       
   108         date_hierarchy = 'submit_date'
       
   109         search_fields = ('comment', 'user__username')
       
   110 
       
   111     def __unicode__(self):
       
   112         return "%s: %s..." % (self.user.username, self.comment[:100])
       
   113 
       
   114     def get_absolute_url(self):
       
   115         try:
       
   116             return self.get_content_object().get_absolute_url() + "#c" + str(self.id)
       
   117         except AttributeError:
       
   118             return ""
       
   119 
       
   120     def get_crossdomain_url(self):
       
   121         return "/r/%d/%d/" % (self.content_type_id, self.object_id)
       
   122 
       
   123     def get_flag_url(self):
       
   124         return "/comments/flag/%s/" % self.id
       
   125 
       
   126     def get_deletion_url(self):
       
   127         return "/comments/delete/%s/" % self.id
       
   128 
       
   129     def get_content_object(self):
       
   130         """
       
   131         Returns the object that this comment is a comment on. Returns None if
       
   132         the object no longer exists.
       
   133         """
       
   134         from django.core.exceptions import ObjectDoesNotExist
       
   135         try:
       
   136             return self.content_type.get_object_for_this_type(pk=self.object_id)
       
   137         except ObjectDoesNotExist:
       
   138             return None
       
   139 
       
   140     get_content_object.short_description = _('Content object')
       
   141 
       
   142     def _fill_karma_cache(self):
       
   143         """Helper function that populates good/bad karma caches."""
       
   144         good, bad = 0, 0
       
   145         for k in self.karmascore_set:
       
   146             if k.score == -1:
       
   147                 bad +=1
       
   148             elif k.score == 1:
       
   149                 good +=1
       
   150         self._karma_total_good, self._karma_total_bad = good, bad
       
   151 
       
   152     def get_good_karma_total(self):
       
   153         if not hasattr(self, "_karma_total_good"):
       
   154             self._fill_karma_cache()
       
   155         return self._karma_total_good
       
   156 
       
   157     def get_bad_karma_total(self):
       
   158         if not hasattr(self, "_karma_total_bad"):
       
   159             self._fill_karma_cache()
       
   160         return self._karma_total_bad
       
   161 
       
   162     def get_karma_total(self):
       
   163         if not hasattr(self, "_karma_total_good") or not hasattr(self, "_karma_total_bad"):
       
   164             self._fill_karma_cache()
       
   165         return self._karma_total_good + self._karma_total_bad
       
   166 
       
   167     def get_as_text(self):
       
   168         return _('Posted by %(user)s at %(date)s\n\n%(comment)s\n\nhttp://%(domain)s%(url)s') % \
       
   169             {'user': self.user.username, 'date': self.submit_date,
       
   170             'comment': self.comment, 'domain': self.site.domain, 'url': self.get_absolute_url()}
       
   171 
       
   172 
       
   173 class FreeComment(models.Model):
       
   174     """A comment by a non-registered user."""
       
   175     content_type = models.ForeignKey(ContentType)
       
   176     object_id = models.IntegerField(_('object ID'))
       
   177     comment = models.TextField(_('comment'), max_length=3000)
       
   178     person_name = models.CharField(_("person's name"), max_length=50)
       
   179     submit_date = models.DateTimeField(_('date/time submitted'), auto_now_add=True)
       
   180     is_public = models.BooleanField(_('is public'))
       
   181     ip_address = models.IPAddressField(_('ip address'))
       
   182     # TODO: Change this to is_removed, like Comment
       
   183     approved = models.BooleanField(_('approved by staff'))
       
   184     site = models.ForeignKey(Site)
       
   185 
       
   186     class Meta:
       
   187         verbose_name = _('free comment')
       
   188         verbose_name_plural = _('free comments')
       
   189         ordering = ('-submit_date',)
       
   190 
       
   191     class Admin:
       
   192         fields = (
       
   193             (None, {'fields': ('content_type', 'object_id', 'site')}),
       
   194             ('Content', {'fields': ('person_name', 'comment')}),
       
   195             ('Meta', {'fields': ('submit_date', 'is_public', 'ip_address', 'approved')}),
       
   196         )
       
   197         list_display = ('person_name', 'submit_date', 'content_type', 'get_content_object')
       
   198         list_filter = ('submit_date',)
       
   199         date_hierarchy = 'submit_date'
       
   200         search_fields = ('comment', 'person_name')
       
   201 
       
   202     def __unicode__(self):
       
   203         return "%s: %s..." % (self.person_name, self.comment[:100])
       
   204 
       
   205     def get_absolute_url(self):
       
   206         try:
       
   207             return self.get_content_object().get_absolute_url() + "#c" + str(self.id)
       
   208         except AttributeError:
       
   209             return ""
       
   210 
       
   211     def get_content_object(self):
       
   212         """
       
   213         Returns the object that this comment is a comment on. Returns None if
       
   214         the object no longer exists.
       
   215         """
       
   216         from django.core.exceptions import ObjectDoesNotExist
       
   217         try:
       
   218             return self.content_type.get_object_for_this_type(pk=self.object_id)
       
   219         except ObjectDoesNotExist:
       
   220             return None
       
   221 
       
   222     get_content_object.short_description = _('Content object')
       
   223 
       
   224 
       
   225 class KarmaScoreManager(models.Manager):
       
   226     def vote(self, user_id, comment_id, score):
       
   227         try:
       
   228             karma = self.get(comment__pk=comment_id, user__pk=user_id)
       
   229         except self.model.DoesNotExist:
       
   230             karma = self.model(None, user_id=user_id, comment_id=comment_id, score=score, scored_date=datetime.datetime.now())
       
   231             karma.save()
       
   232         else:
       
   233             karma.score = score
       
   234             karma.scored_date = datetime.datetime.now()
       
   235             karma.save()
       
   236 
       
   237     def get_pretty_score(self, score):
       
   238         """
       
   239         Given a score between -1 and 1 (inclusive), returns the same score on a
       
   240         scale between 1 and 10 (inclusive), as an integer.
       
   241         """
       
   242         if score is None:
       
   243             return DEFAULT_KARMA
       
   244         return int(round((4.5 * score) + 5.5))
       
   245 
       
   246 
       
   247 class KarmaScore(models.Model):
       
   248     user = models.ForeignKey(User)
       
   249     comment = models.ForeignKey(Comment)
       
   250     score = models.SmallIntegerField(_('score'), db_index=True)
       
   251     scored_date = models.DateTimeField(_('score date'), auto_now=True)
       
   252     objects = KarmaScoreManager()
       
   253 
       
   254     class Meta:
       
   255         verbose_name = _('karma score')
       
   256         verbose_name_plural = _('karma scores')
       
   257         unique_together = (('user', 'comment'),)
       
   258 
       
   259     def __unicode__(self):
       
   260         return _("%(score)d rating by %(user)s") % {'score': self.score, 'user': self.user}
       
   261 
       
   262 
       
   263 class UserFlagManager(models.Manager):
       
   264     def flag(self, comment, user):
       
   265         """
       
   266         Flags the given comment by the given user. If the comment has already
       
   267         been flagged by the user, or it was a comment posted by the user,
       
   268         nothing happens.
       
   269         """
       
   270         if int(comment.user_id) == int(user.id):
       
   271             return # A user can't flag his own comment. Fail silently.
       
   272         try:
       
   273             f = self.get(user__pk=user.id, comment__pk=comment.id)
       
   274         except self.model.DoesNotExist:
       
   275             from django.core.mail import mail_managers
       
   276             f = self.model(None, user.id, comment.id, None)
       
   277             message = _('This comment was flagged by %(user)s:\n\n%(text)s') % {'user': user.username, 'text': comment.get_as_text()}
       
   278             mail_managers('Comment flagged', message, fail_silently=True)
       
   279             f.save()
       
   280 
       
   281 
       
   282 class UserFlag(models.Model):
       
   283     user = models.ForeignKey(User)
       
   284     comment = models.ForeignKey(Comment)
       
   285     flag_date = models.DateTimeField(_('flag date'), auto_now_add=True)
       
   286     objects = UserFlagManager()
       
   287 
       
   288     class Meta:
       
   289         verbose_name = _('user flag')
       
   290         verbose_name_plural = _('user flags')
       
   291         unique_together = (('user', 'comment'),)
       
   292 
       
   293     def __unicode__(self):
       
   294         return _("Flag by %r") % self.user
       
   295 
       
   296 
       
   297 class ModeratorDeletion(models.Model):
       
   298     user = models.ForeignKey(User, verbose_name='moderator')
       
   299     comment = models.ForeignKey(Comment)
       
   300     deletion_date = models.DateTimeField(_('deletion date'), auto_now_add=True)
       
   301 
       
   302     class Meta:
       
   303         verbose_name = _('moderator deletion')
       
   304         verbose_name_plural = _('moderator deletions')
       
   305         unique_together = (('user', 'comment'),)
       
   306 
       
   307     def __unicode__(self):
       
   308         return _("Moderator deletion by %r") % self.user