This is a continuation of tutorial series on django-comments-xtd react plugin and integrating recaptcha into blog.
In this tutorial, we are going to integrate reCAPTCHA v2.0 into django-comments-xtd javascript plugin to make some sort of spam protection.
The basic idea is not to let the users(or robots) leave comments until they are verified by reCAPTCHA v2.0. The most complicated part for me was building the connection between react part and Django since there was no much information on how it should exactly be implemented in django-comments-xtd.
This tutorial is based on the previous recaptcha tutorial code base that can be found at my repository.
django-comments-xtd API does not support Recaptcha integration natively but we can redesign the part that is responsible for processing the comments stuff. For example, API that processes comment creation is located here:
# views.py from django-comments-xtd api
class CommentCreate(generics.CreateAPIView):
"""Create a comment."""
serializer_class = serializers.WriteCommentSerializer
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
if serializer.is_valid():
response = super(CommentCreate, self).post(request, *args, **kwargs)
else:
if 'non_field_errors' in serializer.errors:
response_msg = serializer.errors['non_field_errors'][0]
else:
response_msg = [k for k in six.iterkeys(serializer.errors)]
return Response(response_msg, status=400)
if self.resp_dict['code'] == 201: # The comment has been created.
return response
elif self.resp_dict['code'] in [202, 204, 403]:
return Response({}, status=self.resp_dict['code'])
def perform_create(self, serializer):
self.resp_dict = serializer.save()
Basically what we are going to do here is to create a new API view class inherited from CommentCreate, override method post so that it will perform Recaptcha result validation in the first place and then if everything is ok it will delegate to its parent(CommentCreate) method post.
Let's create our custom API class. First, create file test_blog/blog/api/views.py with the following content:
from django_comments_xtd.api import CommentCreate
from rest_framework.response import Response
from django.contrib import messages
class CustomCommentCreate(CommentCreate):
def post(self, request, *args, **kwargs):
if request.recaptcha_is_valid:
return super(CustomCommentCreate, self).post(request, *args, **kwargs)
storage = messages.get_messages(request)
errors = []
for message in storage:
errors.append(message.message)
pass
storage.used = True
return Response(errors, status=403)
Also, don't forget to create an empty init.py file in the API folder to make your new classes from that folder available to other parts of your app.
Let's break down what we have done by creating views.py. First, as I mentioned before we created a custom API class CustomCommentCreate, then we overrode the post method, where we added Recaptcha result validation if request.recaptcha_is_valid. recaptcha_is_valid is going to be assigned earlier by the decorator that we implemented in the previous tutorial. Then if we have some errors they will be stored in the errors and will be sent along with the status 403.
Now we need to redirect API calls from the previous API to our new API. Let's go to the test_blog/urls.py and add the following:
...
from blog.api.views import CustomCommentCreate # Importing our api view
from test_blog.decorators import check_recaptcha # Importing our decorator for recaptcha validation
...
urlpatterns = [
path('admin/', admin.site.urls),
# Redirecting api call to our custom view
path('comments/api/comment/', check_recaptcha(CustomCommentCreate().as_view()), name='comments-xtd-api-create'),
path('comments/', include('django_comments_xtd.urls')),
...
At this step, you can try to launch the blog and try to post comments on some test articles. You will notice that will always result in some error:
That is because Recaptcha part in our new API is not validated and it sends an error. Let's fix this by adding the client side.
This part definitely requires you to go through the previous tutorial on django-comments-xtd.
In this part, we will need to integrate reCAPTCHA somewhere here.
First, let's install recaptcha react widget. Go to test_blog/frontend/django-comments-xtd folder and run the following command:
npm install --save react-google-recaptcha
Now open file src/commentform.jsx and add in the beginning the following:
...
import ReCAPTCHA from "react-google-recaptcha";
...
Below at the end of the constructor method add the reference which we will use later for the referencing to Recaptcha widget:
constructor(props) {
super(props);
this.state = {
name: '', email: '', url: '', followup: false, comment: '',
reply_to: this.props.reply_to || 0,
visited: {name: false, email: false, comment: false},
errors: {name: false, email: false, comment: false},
previewing: false,
alert: {message: '', cssc: ''}
};
this.handle_input_change = this.handle_input_change.bind(this);
this.handle_blur = this.handle_blur.bind(this);
this.handle_submit = this.handle_submit.bind(this);
this.handle_preview = this.handle_preview.bind(this);
this.reset_form = this.reset_form.bind(this);
this.fhelp = <span className="form-text small invalid-feedback">
{django.gettext("This field is required.")}
</span>;
this.recaptchaRef = React.createRef(); // This is the reference to recaptcha widget object
}
Now go to render_form method and insert Recaptcha widget somewhere above the send button:
...
<div className={btns_row_class}>
<div className="offset-md-3 col-md-7">
<ReCAPTCHA
ref={this.recaptchaRef}
className="g-recaptcha"
sitekey="6Lcpqa4ZAAAAAPxNuoc7b81Tljx7bebrfP-Eq2ex" // here should be your site key
hl="en"
/>
<button type="submit" name="post"
className={btn_submit_class}>{btn_label_send}</button>
<button name="preview" className={btn_preview_class}
onClick={this.handle_preview}>{btn_label_preview}</button>
</div>
</div>
...
Now we need to pack Recaptcha stuff into our request when the send button is hit. Go to handle_submit method and let's update data that is sent to the server with:
const data = {
content_type: this.props.form.content_type,
object_pk: this.props.form.object_pk,
timestamp: this.props.form.timestamp,
security_hash: this.props.form.security_hash,
honeypot: '',
comment: this.state.comment,
name: this.state.name,
email: this.state.email,
url: this.state.url,
followup: this.state.followup,
reply_to: this.state.reply_to,
'g-recaptcha-response': this.recaptchaRef.current.getValue() // this is recaptcha response value for the server validation
};
'g-recaptcha-response' is the thing that is going to be validated on the server side via our decorator stuff, etc.
Let's build our client side. Go back from the src folder and run the build command:
npm run build
Run the application and test how the comments work. Don't forget to hit "I'm not a robot" otherwise you are robot;-)
Perfect!
I hope everything was clear. Probably there are a lot of references to the previous tutorials but I did not want to start every tutorial from scratch since they are tightly connected but at the same time they are separate.
What can we improve here? The client side errors processing. Now it just says "Sorry, your comment has been rejected". This should be processed in the method handle_submit in the 403 case. I did the same for this blog. If you want to try out how it works you can leave the test comment below or share your thoughts on this tutorial or whatever you like to leave.
The source code of this tutorial is available at the repository.
Usually a blogger puts here some relative articles. I'm working on creating more interesting content. As soon as I have some related content I will implement some logic here. Below you may find some comments. If there is no any, it is your chance to leave the first comment:) PS.I'm glad you are here and reading this^^