blob: ba59cbafc964060536709c885b1fe014b4d69170 [file] [log] [blame]
import base64
import datetime
from django.core import validators
from django import oldforms
from django.core.mail import mail_admins, mail_managers
from django.http import Http404
from django.core.exceptions import ObjectDoesNotExist
from django.shortcuts import render_to_response
from django.template import RequestContext
from django.contrib.comments.models import Comment, FreeComment, RATINGS_REQUIRED, RATINGS_OPTIONAL, IS_PUBLIC
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth import authenticate
from django.http import HttpResponseRedirect
from django.utils.text import normalize_newlines
from django.conf import settings
from django.utils.translation import ungettext, ugettext as _
from django.utils.encoding import smart_unicode
COMMENTS_PER_PAGE = 20
# TODO: This is a copy of the manipulator-based form that used to live in
# contrib.auth.forms. It should be replaced with the newforms version that
# has now been added to contrib.auth.forms when the comments app gets updated
# for newforms.
class AuthenticationForm(oldforms.Manipulator):
"""
Base class for authenticating users. Extend this to get a form that accepts
username/password logins.
"""
def __init__(self, request=None):
"""
If request is passed in, the manipulator will validate that cookies are
enabled. Note that the request (a HttpRequest object) must have set a
cookie with the key TEST_COOKIE_NAME and value TEST_COOKIE_VALUE before
running this validator.
"""
self.request = request
self.fields = [
oldforms.TextField(field_name="username", length=15, max_length=30, is_required=True,
validator_list=[self.isValidUser, self.hasCookiesEnabled]),
oldforms.PasswordField(field_name="password", length=15, max_length=30, is_required=True),
]
self.user_cache = None
def hasCookiesEnabled(self, field_data, all_data):
if self.request and not self.request.session.test_cookie_worked():
raise validators.ValidationError, _("Your Web browser doesn't appear to have cookies enabled. Cookies are required for logging in.")
def isValidUser(self, field_data, all_data):
username = field_data
password = all_data.get('password', None)
self.user_cache = authenticate(username=username, password=password)
if self.user_cache is None:
raise validators.ValidationError, _("Please enter a correct username and password. Note that both fields are case-sensitive.")
elif not self.user_cache.is_active:
raise validators.ValidationError, _("This account is inactive.")
def get_user_id(self):
if self.user_cache:
return self.user_cache.id
return None
def get_user(self):
return self.user_cache
class PublicCommentManipulator(AuthenticationForm):
"Manipulator that handles public registered comments"
def __init__(self, user, ratings_required, ratings_range, num_rating_choices):
AuthenticationForm.__init__(self)
self.ratings_range, self.num_rating_choices = ratings_range, num_rating_choices
choices = [(c, c) for c in ratings_range]
def get_validator_list(rating_num):
if rating_num <= num_rating_choices:
return [validators.RequiredIfOtherFieldsGiven(['rating%d' % i for i in range(1, 9) if i != rating_num], _("This rating is required because you've entered at least one other rating."))]
else:
return []
self.fields.extend([
oldforms.LargeTextField(field_name="comment", max_length=3000, is_required=True,
validator_list=[self.hasNoProfanities]),
oldforms.RadioSelectField(field_name="rating1", choices=choices,
is_required=ratings_required and num_rating_choices > 0,
validator_list=get_validator_list(1),
),
oldforms.RadioSelectField(field_name="rating2", choices=choices,
is_required=ratings_required and num_rating_choices > 1,
validator_list=get_validator_list(2),
),
oldforms.RadioSelectField(field_name="rating3", choices=choices,
is_required=ratings_required and num_rating_choices > 2,
validator_list=get_validator_list(3),
),
oldforms.RadioSelectField(field_name="rating4", choices=choices,
is_required=ratings_required and num_rating_choices > 3,
validator_list=get_validator_list(4),
),
oldforms.RadioSelectField(field_name="rating5", choices=choices,
is_required=ratings_required and num_rating_choices > 4,
validator_list=get_validator_list(5),
),
oldforms.RadioSelectField(field_name="rating6", choices=choices,
is_required=ratings_required and num_rating_choices > 5,
validator_list=get_validator_list(6),
),
oldforms.RadioSelectField(field_name="rating7", choices=choices,
is_required=ratings_required and num_rating_choices > 6,
validator_list=get_validator_list(7),
),
oldforms.RadioSelectField(field_name="rating8", choices=choices,
is_required=ratings_required and num_rating_choices > 7,
validator_list=get_validator_list(8),
),
])
if user.is_authenticated():
self["username"].is_required = False
self["username"].validator_list = []
self["password"].is_required = False
self["password"].validator_list = []
self.user_cache = user
def hasNoProfanities(self, field_data, all_data):
if settings.COMMENTS_ALLOW_PROFANITIES:
return
return validators.hasNoProfanities(field_data, all_data)
def get_comment(self, new_data):
"Helper function"
return Comment(None, self.get_user_id(), new_data["content_type_id"],
new_data["object_id"], new_data.get("headline", "").strip(),
new_data["comment"].strip(), new_data.get("rating1", None),
new_data.get("rating2", None), new_data.get("rating3", None),
new_data.get("rating4", None), new_data.get("rating5", None),
new_data.get("rating6", None), new_data.get("rating7", None),
new_data.get("rating8", None), new_data.get("rating1", None) is not None,
datetime.datetime.now(), new_data["is_public"], new_data["ip_address"], False, settings.SITE_ID)
def save(self, new_data):
today = datetime.date.today()
c = self.get_comment(new_data)
for old in Comment.objects.filter(content_type__id__exact=new_data["content_type_id"],
object_id__exact=new_data["object_id"], user__id__exact=self.get_user_id()):
# Check that this comment isn't duplicate. (Sometimes people post
# comments twice by mistake.) If it is, fail silently by pretending
# the comment was posted successfully.
if old.submit_date.date() == today and old.comment == c.comment \
and old.rating1 == c.rating1 and old.rating2 == c.rating2 \
and old.rating3 == c.rating3 and old.rating4 == c.rating4 \
and old.rating5 == c.rating5 and old.rating6 == c.rating6 \
and old.rating7 == c.rating7 and old.rating8 == c.rating8:
return old
# If the user is leaving a rating, invalidate all old ratings.
if c.rating1 is not None:
old.valid_rating = False
old.save()
c.save()
# If the commentor has posted fewer than COMMENTS_FIRST_FEW comments,
# send the comment to the managers.
if self.user_cache.comment_set.count() <= settings.COMMENTS_FIRST_FEW:
message = ungettext('This comment was posted by a user who has posted fewer than %(count)s comment:\n\n%(text)s',
'This comment was posted by a user who has posted fewer than %(count)s comments:\n\n%(text)s', settings.COMMENTS_FIRST_FEW) % \
{'count': settings.COMMENTS_FIRST_FEW, 'text': c.get_as_text()}
mail_managers("Comment posted by rookie user", message)
if settings.COMMENTS_SKETCHY_USERS_GROUP and settings.COMMENTS_SKETCHY_USERS_GROUP in [g.id for g in self.user_cache.groups.all()]:
message = _('This comment was posted by a sketchy user:\n\n%(text)s') % {'text': c.get_as_text()}
mail_managers("Comment posted by sketchy user (%s)" % self.user_cache.username, c.get_as_text())
return c
class PublicFreeCommentManipulator(oldforms.Manipulator):
"Manipulator that handles public free (unregistered) comments"
def __init__(self):
self.fields = (
oldforms.TextField(field_name="person_name", max_length=50, is_required=True,
validator_list=[self.hasNoProfanities]),
oldforms.LargeTextField(field_name="comment", max_length=3000, is_required=True,
validator_list=[self.hasNoProfanities]),
)
def hasNoProfanities(self, field_data, all_data):
if settings.COMMENTS_ALLOW_PROFANITIES:
return
return validators.hasNoProfanities(field_data, all_data)
def get_comment(self, new_data):
"Helper function"
return FreeComment(None, new_data["content_type_id"],
new_data["object_id"], new_data["comment"].strip(),
new_data["person_name"].strip(), datetime.datetime.now(), new_data["is_public"],
new_data["ip_address"], False, settings.SITE_ID)
def save(self, new_data):
today = datetime.date.today()
c = self.get_comment(new_data)
# Check that this comment isn't duplicate. (Sometimes people post
# comments twice by mistake.) If it is, fail silently by pretending
# the comment was posted successfully.
for old_comment in FreeComment.objects.filter(content_type__id__exact=new_data["content_type_id"],
object_id__exact=new_data["object_id"], person_name__exact=new_data["person_name"],
submit_date__year=today.year, submit_date__month=today.month,
submit_date__day=today.day):
if old_comment.comment == c.comment:
return old_comment
c.save()
return c
def post_comment(request, extra_context=None, context_processors=None):
"""
Post a comment
Redirects to the `comments.comments.comment_was_posted` view upon success.
Templates: `comment_preview`
Context:
comment
the comment being posted
comment_form
the comment form
options
comment options
target
comment target
hash
security hash (must be included in a posted form to succesfully
post a comment).
rating_options
comment ratings options
ratings_optional
are ratings optional?
ratings_required
are ratings required?
rating_range
range of ratings
rating_choices
choice of ratings
"""
if extra_context is None: extra_context = {}
if not request.POST:
raise Http404, _("Only POSTs are allowed")
try:
options, target, security_hash = request.POST['options'], request.POST['target'], request.POST['gonzo']
except KeyError:
raise Http404, _("One or more of the required fields wasn't submitted")
photo_options = request.POST.get('photo_options', '')
rating_options = normalize_newlines(request.POST.get('rating_options', ''))
if Comment.objects.get_security_hash(options, photo_options, rating_options, target) != security_hash:
raise Http404, _("Somebody tampered with the comment form (security violation)")
# Now we can be assured the data is valid.
if rating_options:
rating_range, rating_choices = Comment.objects.get_rating_options(base64.decodestring(rating_options))
else:
rating_range, rating_choices = [], []
content_type_id, object_id = target.split(':') # target is something like '52:5157'
try:
obj = ContentType.objects.get(pk=content_type_id).get_object_for_this_type(pk=object_id)
except ObjectDoesNotExist:
raise Http404, _("The comment form had an invalid 'target' parameter -- the object ID was invalid")
option_list = options.split(',') # options is something like 'pa,ra'
new_data = request.POST.copy()
new_data['content_type_id'] = content_type_id
new_data['object_id'] = object_id
new_data['ip_address'] = request.META.get('REMOTE_ADDR')
new_data['is_public'] = IS_PUBLIC in option_list
manipulator = PublicCommentManipulator(request.user,
ratings_required=RATINGS_REQUIRED in option_list,
ratings_range=rating_range,
num_rating_choices=len(rating_choices))
errors = manipulator.get_validation_errors(new_data)
# If user gave correct username/password and wasn't already logged in, log them in
# so they don't have to enter a username/password again.
if manipulator.get_user() and not manipulator.get_user().is_authenticated() and 'password' in new_data and manipulator.get_user().check_password(new_data['password']):
from django.contrib.auth import login
login(request, manipulator.get_user())
if errors or 'preview' in request.POST:
class CommentFormWrapper(oldforms.FormWrapper):
def __init__(self, manipulator, new_data, errors, rating_choices):
oldforms.FormWrapper.__init__(self, manipulator, new_data, errors)
self.rating_choices = rating_choices
def ratings(self):
field_list = [self['rating%d' % (i+1)] for i in range(len(rating_choices))]
for i, f in enumerate(field_list):
f.choice = rating_choices[i]
return field_list
comment = errors and '' or manipulator.get_comment(new_data)
comment_form = CommentFormWrapper(manipulator, new_data, errors, rating_choices)
return render_to_response('comments/preview.html', {
'comment': comment,
'comment_form': comment_form,
'options': options,
'target': target,
'hash': security_hash,
'rating_options': rating_options,
'ratings_optional': RATINGS_OPTIONAL in option_list,
'ratings_required': RATINGS_REQUIRED in option_list,
'rating_range': rating_range,
'rating_choices': rating_choices,
}, context_instance=RequestContext(request, extra_context, context_processors))
elif 'post' in request.POST:
# If the IP is banned, mail the admins, do NOT save the comment, and
# serve up the "Thanks for posting" page as if the comment WAS posted.
if request.META['REMOTE_ADDR'] in settings.BANNED_IPS:
mail_admins("Banned IP attempted to post comment", smart_unicode(request.POST) + "\n\n" + str(request.META))
else:
manipulator.do_html2python(new_data)
comment = manipulator.save(new_data)
return HttpResponseRedirect("../posted/?c=%s:%s" % (content_type_id, object_id))
else:
raise Http404, _("The comment form didn't provide either 'preview' or 'post'")
def post_free_comment(request, extra_context=None, context_processors=None):
"""
Post a free comment (not requiring a log in)
Redirects to `comments.comments.comment_was_posted` view on success.
Templates: `comment_free_preview`
Context:
comment
comment being posted
comment_form
comment form object
options
comment options
target
comment target
hash
security hash (must be included in a posted form to succesfully
post a comment).
"""
if extra_context is None: extra_context = {}
if not request.POST:
raise Http404, _("Only POSTs are allowed")
try:
options, target, security_hash = request.POST['options'], request.POST['target'], request.POST['gonzo']
except KeyError:
raise Http404, _("One or more of the required fields wasn't submitted")
if Comment.objects.get_security_hash(options, '', '', target) != security_hash:
raise Http404, _("Somebody tampered with the comment form (security violation)")
content_type_id, object_id = target.split(':') # target is something like '52:5157'
content_type = ContentType.objects.get(pk=content_type_id)
try:
obj = content_type.get_object_for_this_type(pk=object_id)
except ObjectDoesNotExist:
raise Http404, _("The comment form had an invalid 'target' parameter -- the object ID was invalid")
option_list = options.split(',')
new_data = request.POST.copy()
new_data['content_type_id'] = content_type_id
new_data['object_id'] = object_id
new_data['ip_address'] = request.META['REMOTE_ADDR']
new_data['is_public'] = IS_PUBLIC in option_list
manipulator = PublicFreeCommentManipulator()
errors = manipulator.get_validation_errors(new_data)
if errors or 'preview' in request.POST:
comment = errors and '' or manipulator.get_comment(new_data)
return render_to_response('comments/free_preview.html', {
'comment': comment,
'comment_form': oldforms.FormWrapper(manipulator, new_data, errors),
'options': options,
'target': target,
'hash': security_hash,
}, context_instance=RequestContext(request, extra_context, context_processors))
elif 'post' in request.POST:
# If the IP is banned, mail the admins, do NOT save the comment, and
# serve up the "Thanks for posting" page as if the comment WAS posted.
if request.META['REMOTE_ADDR'] in settings.BANNED_IPS:
from django.core.mail import mail_admins
mail_admins("Practical joker", smart_unicode(request.POST) + "\n\n" + str(request.META))
else:
manipulator.do_html2python(new_data)
comment = manipulator.save(new_data)
return HttpResponseRedirect("../posted/?c=%s:%s" % (content_type_id, object_id))
else:
raise Http404, _("The comment form didn't provide either 'preview' or 'post'")
def comment_was_posted(request, extra_context=None, context_processors=None):
"""
Display "comment was posted" success page
Templates: `comment_posted`
Context:
object
The object the comment was posted on
"""
if extra_context is None: extra_context = {}
obj = None
if 'c' in request.GET:
content_type_id, object_id = request.GET['c'].split(':')
try:
content_type = ContentType.objects.get(pk=content_type_id)
obj = content_type.get_object_for_this_type(pk=object_id)
except ObjectDoesNotExist:
pass
return render_to_response('comments/posted.html', {'object': obj},
context_instance=RequestContext(request, extra_context, context_processors))