web/lib/django/contrib/comments/forms.py
changeset 0 0d40e90630ef
child 29 cc9b7e14412b
equal deleted inserted replaced
-1:000000000000 0:0d40e90630ef
       
     1 import time
       
     2 import datetime
       
     3 
       
     4 from django import forms
       
     5 from django.forms.util import ErrorDict
       
     6 from django.conf import settings
       
     7 from django.contrib.contenttypes.models import ContentType
       
     8 from models import Comment
       
     9 from django.utils.encoding import force_unicode
       
    10 from django.utils.hashcompat import sha_constructor
       
    11 from django.utils.text import get_text_list
       
    12 from django.utils.translation import ungettext, ugettext_lazy as _
       
    13 
       
    14 COMMENT_MAX_LENGTH = getattr(settings,'COMMENT_MAX_LENGTH', 3000)
       
    15 
       
    16 class CommentSecurityForm(forms.Form):
       
    17     """
       
    18     Handles the security aspects (anti-spoofing) for comment forms.
       
    19     """
       
    20     content_type  = forms.CharField(widget=forms.HiddenInput)
       
    21     object_pk     = forms.CharField(widget=forms.HiddenInput)
       
    22     timestamp     = forms.IntegerField(widget=forms.HiddenInput)
       
    23     security_hash = forms.CharField(min_length=40, max_length=40, widget=forms.HiddenInput)
       
    24 
       
    25     def __init__(self, target_object, data=None, initial=None):
       
    26         self.target_object = target_object
       
    27         if initial is None:
       
    28             initial = {}
       
    29         initial.update(self.generate_security_data())
       
    30         super(CommentSecurityForm, self).__init__(data=data, initial=initial)
       
    31         
       
    32     def security_errors(self):
       
    33         """Return just those errors associated with security"""
       
    34         errors = ErrorDict()
       
    35         for f in ["honeypot", "timestamp", "security_hash"]:
       
    36             if f in self.errors:
       
    37                 errors[f] = self.errors[f]
       
    38         return errors
       
    39 
       
    40     def clean_security_hash(self):
       
    41         """Check the security hash."""
       
    42         security_hash_dict = {
       
    43             'content_type' : self.data.get("content_type", ""),
       
    44             'object_pk' : self.data.get("object_pk", ""),
       
    45             'timestamp' : self.data.get("timestamp", ""),
       
    46         }
       
    47         expected_hash = self.generate_security_hash(**security_hash_dict)
       
    48         actual_hash = self.cleaned_data["security_hash"]
       
    49         if expected_hash != actual_hash:
       
    50             raise forms.ValidationError("Security hash check failed.")
       
    51         return actual_hash
       
    52 
       
    53     def clean_timestamp(self):
       
    54         """Make sure the timestamp isn't too far (> 2 hours) in the past."""
       
    55         ts = self.cleaned_data["timestamp"]
       
    56         if time.time() - ts > (2 * 60 * 60):
       
    57             raise forms.ValidationError("Timestamp check failed")
       
    58         return ts
       
    59 
       
    60     def generate_security_data(self):
       
    61         """Generate a dict of security data for "initial" data."""
       
    62         timestamp = int(time.time())
       
    63         security_dict =   {
       
    64             'content_type'  : str(self.target_object._meta),
       
    65             'object_pk'     : str(self.target_object._get_pk_val()),
       
    66             'timestamp'     : str(timestamp),
       
    67             'security_hash' : self.initial_security_hash(timestamp),
       
    68         }
       
    69         return security_dict
       
    70 
       
    71     def initial_security_hash(self, timestamp):
       
    72         """
       
    73         Generate the initial security hash from self.content_object
       
    74         and a (unix) timestamp.
       
    75         """
       
    76 
       
    77         initial_security_dict = {
       
    78             'content_type' : str(self.target_object._meta),
       
    79             'object_pk' : str(self.target_object._get_pk_val()),
       
    80             'timestamp' : str(timestamp),
       
    81           }
       
    82         return self.generate_security_hash(**initial_security_dict)
       
    83 
       
    84     def generate_security_hash(self, content_type, object_pk, timestamp):
       
    85         """Generate a (SHA1) security hash from the provided info."""
       
    86         info = (content_type, object_pk, timestamp, settings.SECRET_KEY)
       
    87         return sha_constructor("".join(info)).hexdigest()
       
    88 
       
    89 class CommentDetailsForm(CommentSecurityForm):
       
    90     """
       
    91     Handles the specific details of the comment (name, comment, etc.).
       
    92     """
       
    93     name          = forms.CharField(label=_("Name"), max_length=50)
       
    94     email         = forms.EmailField(label=_("Email address"))
       
    95     url           = forms.URLField(label=_("URL"), required=False)
       
    96     comment       = forms.CharField(label=_('Comment'), widget=forms.Textarea,
       
    97                                     max_length=COMMENT_MAX_LENGTH)
       
    98 
       
    99     def get_comment_object(self):
       
   100         """
       
   101         Return a new (unsaved) comment object based on the information in this
       
   102         form. Assumes that the form is already validated and will throw a
       
   103         ValueError if not.
       
   104 
       
   105         Does not set any of the fields that would come from a Request object
       
   106         (i.e. ``user`` or ``ip_address``).
       
   107         """
       
   108         if not self.is_valid():
       
   109             raise ValueError("get_comment_object may only be called on valid forms")
       
   110         
       
   111         CommentModel = self.get_comment_model()
       
   112         new = CommentModel(**self.get_comment_create_data())
       
   113         new = self.check_for_duplicate_comment(new)
       
   114         
       
   115         return new
       
   116         
       
   117     def get_comment_model(self):
       
   118         """
       
   119         Get the comment model to create with this form. Subclasses in custom
       
   120         comment apps should override this, get_comment_create_data, and perhaps
       
   121         check_for_duplicate_comment to provide custom comment models.
       
   122         """
       
   123         return Comment
       
   124         
       
   125     def get_comment_create_data(self):
       
   126         """
       
   127         Returns the dict of data to be used to create a comment. Subclasses in
       
   128         custom comment apps that override get_comment_model can override this
       
   129         method to add extra fields onto a custom comment model.
       
   130         """
       
   131         return dict(
       
   132             content_type = ContentType.objects.get_for_model(self.target_object),
       
   133             object_pk    = force_unicode(self.target_object._get_pk_val()),
       
   134             user_name    = self.cleaned_data["name"],
       
   135             user_email   = self.cleaned_data["email"],
       
   136             user_url     = self.cleaned_data["url"],
       
   137             comment      = self.cleaned_data["comment"],
       
   138             submit_date  = datetime.datetime.now(),
       
   139             site_id      = settings.SITE_ID,
       
   140             is_public    = True,
       
   141             is_removed   = False,
       
   142         )
       
   143         
       
   144     def check_for_duplicate_comment(self, new):
       
   145         """
       
   146         Check that a submitted comment isn't a duplicate. This might be caused
       
   147         by someone posting a comment twice. If it is a dup, silently return the *previous* comment.
       
   148         """
       
   149         possible_duplicates = self.get_comment_model()._default_manager.filter(
       
   150             content_type = new.content_type,
       
   151             object_pk = new.object_pk,
       
   152             user_name = new.user_name,
       
   153             user_email = new.user_email,
       
   154             user_url = new.user_url,
       
   155         )
       
   156         for old in possible_duplicates:
       
   157             if old.submit_date.date() == new.submit_date.date() and old.comment == new.comment:
       
   158                 return old
       
   159                 
       
   160         return new
       
   161 
       
   162     def clean_comment(self):
       
   163         """
       
   164         If COMMENTS_ALLOW_PROFANITIES is False, check that the comment doesn't
       
   165         contain anything in PROFANITIES_LIST.
       
   166         """
       
   167         comment = self.cleaned_data["comment"]
       
   168         if settings.COMMENTS_ALLOW_PROFANITIES == False:
       
   169             bad_words = [w for w in settings.PROFANITIES_LIST if w in comment.lower()]
       
   170             if bad_words:
       
   171                 plural = len(bad_words) > 1
       
   172                 raise forms.ValidationError(ungettext(
       
   173                     "Watch your mouth! The word %s is not allowed here.",
       
   174                     "Watch your mouth! The words %s are not allowed here.", plural) % \
       
   175                     get_text_list(['"%s%s%s"' % (i[0], '-'*(len(i)-2), i[-1]) for i in bad_words], 'and'))
       
   176         return comment
       
   177 
       
   178 class CommentForm(CommentDetailsForm):
       
   179     honeypot      = forms.CharField(required=False,
       
   180                                     label=_('If you enter anything in this field '\
       
   181                                             'your comment will be treated as spam'))
       
   182 
       
   183     def clean_honeypot(self):
       
   184         """Check that nothing's been entered into the honeypot."""
       
   185         value = self.cleaned_data["honeypot"]
       
   186         if value:
       
   187             raise forms.ValidationError(self.fields["honeypot"].label)
       
   188         return value