вторник, 26 мая 2009 г.

reCaptcha в django.contrib.comments

При рассмотрении механизма reCaptcha как антиспамерский метод защиты сразу же возникло желание внедрить его в стандартный механизм комментариев Django. Сказать по правде, мне нравятся комментарии в Django, в них заложена именно та гибкость которая позволяет легко менять основной функционал.

На просторах интернета описано множество решений использования reCaptcha в проектах. Правда все они мне показались "разляпистые", одни из них предлагали менять шаблон + вьюху, другие - форму + шаблон, и т.д. Рассматривая каждый из них ощущалась какая то незаконченность и вроде бы они все работают, но душа требовала красоты и порядка :).

После нескольких дней поиска наткнулся на весьма оригинальное решение описаниннное Josh VanderLinden. Заключалось оно в том что нужно было метод Comment.__init__ обвернуть декоратором и в нем реализовать логику добавления captcha field.

И вот уже мысли воспарили, пальцы начали стучать по клавиатуре...
Подключил файлы для работы с reCaptcha, импортнув класс ReCaptchaField появился следующий код:

Файл - urls.py
# MAGIC code by Josh Vanderlinden
from django.contrib.comments.forms import CommentForm
from test_app.recaptcha_forms import RecaptchaField

def add_captcha(func):
def wrapped(self, target_object, *args, **kwargs):
func(self, target_object, *args, **kwargs)
self.fields['security_code'] = 
RecaptchaField(remote_ip = ???????)
return wrapped

CommentForm.__init__ = add_captcha(CommentForm.__init__)
и мысли уперлись казалось в непреодолимый барьер знаков вопроса "???????". Для ReCaptchaField необходим был ip пользователя :(.

Мне на выручку пришел друг и его движок для блогов Byteflow. Фича заключалась в том что бы подключить в MIDDLEWARE_CLASSES реализацию ThreadLocalsMiddleware, которая запоминала request в текущий поток. Код преобразился в следующие:

Файл - settings.py
MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'test_app.threadlocals.ThreadLocalsMiddleware',
)
Файл - urls.py
from django.contrib.comments.forms import CommentForm
from test_app.recaptcha_newforms import RecaptchaField
from test_app.threadlocals import get_request

def add_captcha(func):
def wrapped(self, target_object, *args, **kwargs):
func(self, target_object, *args, **kwargs)
req = get_request()
if req: 
self.fields['security_code'] = 
RecaptchaField(remote_ip = req.META['REMOTE_ADDR'])
return wrapped

CommentForm.__init__ = add_captcha(CommentForm.__init__)
Но вы не поверите, в очередной раз я не увидел captcha, get_request() возвращал пустой объект (None). Хотя данный метод и работал во всех вьюхах, как то не хотелось реализовывать еще одно решение основанное на изменении view. Вооружившись принтами (print) для дебага пошел процесс скрупулезного поиска ошибки.

Ошибка оказалась сильно банальной, оказывается декоратор запускается в новом потоке :(, ну и по этому реквестом там и не пахло.

Как не хотелось мне наследоваться от CommentForm, но уже другого решения я не видел.

Файл - forms.py
from django.contrib import comments
from test_app.threadlocals import get_request
from test_app import recaptcha_forms
from django.conf import settings
from django.utils.translation import ugettext_lazy as _

class ReCaptchaCommentForm(comments.forms.CommentForm):
"""This form have standart CommentForm functional with reCaptcha field"""
def __init__(self, *args, **kwargs):
super(ReCaptchaCommentForm, self).__init__(*args, **kwargs)
request = get_request()
if (settings.DEBUG and not recaptcha_forms.SKIP_IF_IN_DEBUG_MODE) or not request.user.is_authenticated():
self.fields['security_code'] = recaptcha_forms.RecaptchaField(remote_ip = request.META['REMOTE_ADDR'], 
label=_('Are you human?'), help_text = _('Type the words.'))
Файл - urls.py
from django.contrib import comments
from test_app.forms import ReCaptchaCommentForm

def get_comment_form():
return ReCaptchaCommentForm

comments.get_form = get_comment_form
Все чудесно заработало, а реализация получилось довольно таки компактной.
Кто то может отметить что будет небольшая проблемма с предпросмотром (прийдеться вводить captcha 2 раза), но думаю что данный момент будет обойден если реализовать добавление комментариев через ajax.

Комментариев нет:

Отправить комментарий