Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
22 changed files
with
120 additions
and
1,063 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
*.py[co] |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,177 +1,30 @@ | ||
import time | ||
import datetime | ||
|
||
from django import forms | ||
from django.conf import settings | ||
|
||
from django.contrib.contenttypes.models import ContentType | ||
from django.forms.util import ErrorDict | ||
from django.utils.encoding import force_unicode | ||
from django.utils.hashcompat import sha_constructor | ||
from django.utils.text import get_text_list | ||
from django.utils.translation import ungettext, ugettext_lazy as _ | ||
|
||
from dialogos.models import Comment | ||
|
||
|
||
COMMENT_MAX_LENGTH = getattr(settings, "DIALOGOS_COMMENT_MAX_LENGTH", 3000) | ||
COMMENTS_ALLOW_PROFANITIES = getattr(settings, "DIALOGOS_ALLOW_PROFANITIES", True) | ||
PROFANITIES_LIST = getattr(settings, "DIALOGOS_PROFANITIES_LIST", []) | ||
|
||
|
||
class CommentSecurityForm(forms.Form): | ||
""" | ||
Handles the security aspects (anti-spoofing) for comment forms. | ||
""" | ||
content_type = forms.CharField(widget=forms.HiddenInput) | ||
object_pk = forms.CharField(widget=forms.HiddenInput) | ||
timestamp = forms.IntegerField(widget=forms.HiddenInput) | ||
security_hash = forms.CharField(min_length=40, max_length=40, widget=forms.HiddenInput) | ||
|
||
def __init__(self, target_object, data=None, initial=None): | ||
self.target_object = target_object | ||
if initial is None: | ||
initial = {} | ||
initial.update(self.generate_security_data()) | ||
super(CommentSecurityForm, self).__init__(data=data, initial=initial) | ||
|
||
def security_errors(self): | ||
"""Return just those errors associated with security""" | ||
errors = ErrorDict() | ||
for f in ["honeypot", "timestamp", "security_hash"]: | ||
if f in self.errors: | ||
errors[f] = self.errors[f] | ||
return errors | ||
|
||
def clean_security_hash(self): | ||
"""Check the security hash.""" | ||
security_hash_dict = { | ||
"content_type": self.data.get("content_type", ""), | ||
"object_pk": self.data.get("object_pk", ""), | ||
"timestamp": self.data.get("timestamp", ""), | ||
} | ||
expected_hash = self.generate_security_hash(**security_hash_dict) | ||
actual_hash = self.cleaned_data["security_hash"] | ||
if expected_hash != actual_hash: | ||
raise forms.ValidationError("Security hash check failed.") | ||
return actual_hash | ||
|
||
def clean_timestamp(self): | ||
"""Make sure the timestamp isn't too far (> 2 hours) in the past.""" | ||
ts = self.cleaned_data["timestamp"] | ||
if time.time() - ts > (2 * 60 * 60): | ||
raise forms.ValidationError("Timestamp check failed") | ||
return ts | ||
|
||
def generate_security_data(self): | ||
"""Generate a dict of security data for "initial" data.""" | ||
timestamp = int(time.time()) | ||
security_dict = { | ||
"content_type": str(self.target_object._meta), | ||
"object_pk": str(self.target_object._get_pk_val()), | ||
"timestamp": str(timestamp), | ||
"security_hash": self.initial_security_hash(timestamp), | ||
} | ||
return security_dict | ||
|
||
def initial_security_hash(self, timestamp): | ||
""" | ||
Generate the initial security hash from self.content_object | ||
and a (unix) timestamp. | ||
""" | ||
|
||
initial_security_dict = { | ||
"content_type": str(self.target_object._meta), | ||
"object_pk": str(self.target_object._get_pk_val()), | ||
"timestamp": str(timestamp), | ||
} | ||
return self.generate_security_hash(**initial_security_dict) | ||
|
||
def generate_security_hash(self, content_type, object_pk, timestamp): | ||
"""Generate a (SHA1) security hash from the provided info.""" | ||
info = (content_type, object_pk, timestamp, settings.SECRET_KEY) | ||
return sha_constructor("".join(info)).hexdigest() | ||
|
||
|
||
class CommentDetailsForm(CommentSecurityForm): | ||
""" | ||
Handles the specific details of the comment (name, comment, etc.). | ||
""" | ||
name = forms.CharField(label=_("Name"), max_length=50) | ||
email = forms.EmailField(label=_("Email address")) | ||
url = forms.URLField(label=_("URL"), required=False) | ||
comment = forms.CharField(label=_("Comment"), widget=forms.Textarea, max_length=COMMENT_MAX_LENGTH) | ||
|
||
def get_comment_object(self): | ||
""" | ||
Return a new (unsaved) comment object based on the information in this | ||
form. Assumes that the form is already validated and will throw a | ||
ValueError if not. | ||
Does not set any of the fields that would come from a Request object | ||
(i.e. ``user`` or ``ip_address``). | ||
""" | ||
if not self.is_valid(): | ||
raise ValueError("get_comment_object may only be called on valid forms") | ||
|
||
new = Comment( | ||
content_type = ContentType.objects.get_for_model(self.target_object), | ||
object_pk = force_unicode(self.target_object._get_pk_val()), | ||
author = self.cleaned_data["name"], | ||
email = self.cleaned_data["email"], | ||
website = self.cleaned_data["url"], | ||
comment = self.cleaned_data["comment"], | ||
is_public = True, | ||
is_removed = False | ||
) | ||
new = self.check_for_duplicate_comment(new) | ||
|
||
return new | ||
|
||
|
||
def check_for_duplicate_comment(self, new): | ||
""" | ||
Check that a submitted comment isn't a duplicate. This might be caused | ||
by someone posting a comment twice. If it is a dup, silently return the *previous* comment. | ||
""" | ||
possible_duplicates = Comment._default_manager.using( | ||
self.target_object._state.db | ||
).filter( | ||
content_type = new.content_type, | ||
object_pk = new.object_pk, | ||
author = new.author, | ||
email = new.email, | ||
website = new.website, | ||
) | ||
for old in possible_duplicates: | ||
if old.submit_date.date() == new.submit_date.date() and old.comment == new.comment: | ||
return old | ||
|
||
return new | ||
|
||
def clean_comment(self): | ||
""" | ||
If DIALOGOS_ALLOW_PROFANITIES is False, check that the comment doesn't | ||
contain anything in DIALOGOS_PROFANITIES_LIST. | ||
""" | ||
comment = self.cleaned_data["comment"] | ||
if COMMENTS_ALLOW_PROFANITIES == False: | ||
bad_words = [w for w in PROFANITIES_LIST if w in comment.lower()] | ||
if bad_words: | ||
plural = len(bad_words) > 1 | ||
raise forms.ValidationError(ungettext( | ||
"Watch your mouth! The word %s is not allowed here.", | ||
"Watch your mouth! The words %s are not allowed here.", plural) % \ | ||
get_text_list(['"%s%s%s"' % (i[0], "-"*(len(i)-2), i[-1]) for i in bad_words], "and")) | ||
class BaseCommentForm(forms.ModelForm): | ||
def __init__(self, *args, **kwargs): | ||
self.request = kwargs.pop("request") | ||
self.obj = kwargs.pop("obj") | ||
super(BaseCommentForm, self).__init__(*args, **kwargs) | ||
|
||
def save(self, commit=True): | ||
comment = super(BaseCommentForm, self).save(commit=False) | ||
comment.ip_address = self.request.META.get("REMOTE_ADDR", None) | ||
comment.content_type = ContentType.objects.get_for_model(self.obj) | ||
comment.object_id = self.obj.pk | ||
if commit: | ||
comment.save() | ||
return comment | ||
|
||
|
||
class CommentForm(CommentDetailsForm): | ||
honeypot = forms.CharField(required=False, | ||
label=_("If you enter anything in this field your comment will be treated as spam")) | ||
|
||
def clean_honeypot(self): | ||
"""Check that nothing's been entered into the honeypot.""" | ||
value = self.cleaned_data["honeypot"] | ||
if value: | ||
raise forms.ValidationError(self.fields["honeypot"].label) | ||
return value | ||
class UnauthenticatedCommentForm(BaseCommentForm): | ||
class Meta: | ||
model = Comment | ||
fields = [ | ||
"name", "email", "website", "comment" | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,31 +1,23 @@ | ||
from datetime import datetime | ||
|
||
from django.db import models | ||
from django.conf import settings | ||
|
||
from django.contrib.auth.models import User | ||
from django.contrib.contenttypes import generic | ||
from django.contrib.contenttypes.models import ContentType | ||
from django.utils.translation import ugettext_lazy as _ | ||
|
||
|
||
COMMENT_MAX_LENGTH = getattr(settings,'DIALOGOS_COMMENT_MAX_LENGTH', 3000) | ||
|
||
|
||
class Comment(models.Model): | ||
author = models.ForeignKey(User, null=True, related_name="comments") | ||
|
||
author = models.ForeignKey(User, null=True, blank=True, related_name="comments") | ||
name = models.CharField(max_length=50, null=True, blank=True) | ||
email = models.CharField(max_length=50, null=True, blank=True) | ||
website = models.CharField(max_length=50, null=True, blank=True) | ||
name = models.CharField(max_length=100, blank=True) | ||
email = models.CharField(max_length=255, blank=True) | ||
website = models.CharField(max_length=255, blank=True) | ||
|
||
content_type = models.ForeignKey(ContentType, related_name="content_type_set_for_%(class)s") | ||
object_pk = models.TextField(_("object ID")) | ||
content_object = generic.GenericForeignKey(ct_field="content_type", fk_field="object_pk") | ||
content_type = models.ForeignKey(ContentType) | ||
object_id = models.IntegerField() | ||
|
||
comment = models.TextField(_("comment"), max_length=COMMENT_MAX_LENGTH) | ||
comment = models.TextField() | ||
|
||
submit_date = models.DateTimeField(_("date/time submitted"), default=datetime.now) | ||
ip_address = models.IPAddressField(_("IP address"), blank=True, null=True) | ||
is_public = models.BooleanField(_("is public"), default=True) | ||
is_removed = models.BooleanField(_("is removed"), default=False) | ||
|
||
submit_date = models.DateTimeField(default=datetime.now) | ||
ip_address = models.IPAddressField(null=True) | ||
public = models.BooleanField(default=True) |
Empty file.
Oops, something went wrong.