reCAPTCHA your comments!

By Sergei Alekseev(Fullstack Developer) on Julio 22 2020

Views

7885

#javascript#django#tutorial#web#react

No hay traducción en español. Ahora solo tengo la versión de inglés. Estoy trabajando en traducir todo el contenido que pueda. Gracias por entender!

Introduction

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.

Prerequisites

This tutorial is based on the previous recaptcha tutorial code base that can be found at my repository.

Server Side

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:

Invalid Comment

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.

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>&nbsp;
            <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;-)

Recaptcha Comment Form

Perfect!

Conclusion

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.

Bajo construcción

Usualmente un blogger pone aquí algunos artículos relativos. Estoy trabajando en crear más contenido interesante. Tan pronto como tengo algun contenido relativo implementaré algo de lógica aquí. A continuación puede encontrar algunos comentarios. Si no hay nada, es tu oportunidad de dejar el primer comentario:) PS.Me alegra que esté aquí y lea esto^^

Sergei Alekseev

Fullstack Developer

Discusión