Djangoで、シンプルなCaptchaフィールドの作成

Python - Django
2018年11月19日2:44に更新(約9時間前)
2018年11月5日23:03に作成(約13日前)

旧ブログ移行記事です。

概要

コメント欄等、訪れた人が書き込めるページにはスパムが沸きます。 そういった場合、人間かどうかを確認するCaptchaという仕組みを埋め込むのが一般的です。 有名なものはGoogleが提供しているreCaptchaがありますし、画像で表示された数字を入力する、等もよく見かけますね。 今回はDjangoで、以下のようなシンプルなCaptchaフィールドを作ります。
いぬを感じで書いてくださいという欄がある

答えである犬と書かなければ、以下のようにメッセージが表示されます。
正解じゃないとエラー!

カスタムフォームフィールドの作成

Djangoでは、フォームフィールドを自分で作成することができます。 fields.pyというファイルを作成し、以下のように作りました。

from django import forms


class SimpleCaptchaField(forms.CharField):

    def __init__(self, label='人かどうかの確認', **kwargs):
        super().__init__(label=label, required=True, **kwargs)
        self.widget.attrs['placeholder'] = '「いぬ」を漢字で書いてください'

    def clean(self, value):
        value = super().clean(value)
        if value == '犬':
            return value
        else:
            raise forms.ValidationError('答えが違います!')

forms.Fieldや、文字列ベースのフィールドならばforms.CharFieldを継承した新しいクラスを作り、cleanメソッドを定義すれば作成できます。

今回は入力欄にプレースホルダとして問題を書いておこうと思ったので、__init__内で設定しています。また、labelにデフォルト値をもたせ、required=Trueで空欄を許可しないようにしました。

    def __init__(self, label='人かどうかの確認', **kwargs):
        super().__init__(label=label, required=True, **kwargs)
        self.widget.attrs['placeholder'] = '「いぬ」を漢字で書いてください'

余談ですが、このCaptchaは私のブログで実際に使っています。簡単に使えつつ、非常に強い威力を発揮しています。

value変数には入力された文字列が入りますので、これが答えの「犬」と同じならばreturn valueで正常な処理を、そうでなければValidationErrorでエラーメッセージを表示させます。

    def clean(self, value):
        value = super().clean(value)
        if value == '犬':
            return value
        else:
            raise forms.ValidationError('答えが違います!')

フォームから呼び出す

上で作ったフォームフィールドを利用するのは非常に簡単です。コメント投稿欄として使っているモデルフォームに追加する例を考えます。

今回はBootstrap4を使っていたので、widget引数(とcssのclass)を上書きしています。そのままで良いならば、SimpleCaptchaField()だけで動きます。

from django import forms
from .models import Comment
from .fields import SimpleCaptchaField


class CommentCreateForm(forms.ModelForm):
    """コメント投稿フォーム"""

    # captchaフィールドを付け足すだけ
    captha = SimpleCaptchaField(
        widget=forms.TextInput(attrs={'class': 'form-control'}),
    )

    class Meta:
        model = Comment
        fields = ('name', 'text', 'icon')
        widgets = {
            'name': forms.TextInput(attrs={
                'class': "form-control",
            }),
            'text': forms.Textarea(attrs={
                'class': "form-control",
            }),
            'icon': forms.ClearableFileInput(attrs={
                'class': "form-control-file",
            }),
        }

モデル自体に関係ないが、フォームに何か入力欄等を追加したいといった場合は、今回のようにMetaの外側に追加したいフィールドを定義することがあります。 あとは、このフォームをテンプレートへ渡すだけです。

記事にコメントする