Skip to content

Commit dea2068

Browse files
committed
Added support for Django REST Framework.
Added serializer, modelserializer and validator
1 parent c443661 commit dea2068

File tree

8 files changed

+145
-1
lines changed

8 files changed

+145
-1
lines changed

captcha/serializers.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from rest_framework import serializers
2+
from rest_framework.fields import empty
3+
4+
from captcha.validators import captcha_validate
5+
6+
7+
class CaptchaSerializer(serializers.Serializer):
8+
"""Serializer captcha code and captcha hashkey"""
9+
10+
captcha_code = serializers.CharField(max_length=32, write_only=True, required=True)
11+
captcha_hashkey = serializers.CharField(
12+
max_length=40, write_only=True, required=True
13+
)
14+
15+
def run_validation(self, data=empty):
16+
values = super().run_validation(data=data)
17+
captcha_validate(values["captcha_hashkey"], values["captcha_code"])
18+
return values
19+
20+
21+
class CaptchaModelSerializer(serializers.ModelSerializer):
22+
"""Model serializer captcha code and captcha hashkey"""
23+
24+
captcha_code = serializers.CharField(max_length=32, write_only=True, required=True)
25+
captcha_hashkey = serializers.CharField(
26+
max_length=40, write_only=True, required=True
27+
)
28+
29+
def run_validation(self, data=empty):
30+
values = super().run_validation(data=data)
31+
captcha_validate(values["captcha_hashkey"], values["captcha_code"])
32+
return values

captcha/tests/tests.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,53 @@ def test_empty_pool_fallback(self):
575575
self.assertEqual(CaptchaStore.objects.count(), 1)
576576
settings.CAPTCHA_GET_FROM_POOL = __current_test_get_from_pool_setting
577577

578+
def test_serializer(self):
579+
r = self.client.post(
580+
reverse("captcha-test-serializer"),
581+
dict(
582+
captcha_code=self.default_store.response,
583+
captcha_hashkey=self.default_store.hashkey,
584+
),
585+
)
586+
self.assertEqual(r.status_code, 200)
587+
588+
def test_wrong_serializer(self):
589+
r = self.client.post(
590+
reverse("captcha-test-serializer"),
591+
dict(
592+
captcha_code="xxx",
593+
captcha_hashkey="wrong hash",
594+
),
595+
)
596+
self.assertEqual(r.status_code, 400)
597+
self.assertEqual(json.loads(r.content), {"error": "Invalid CAPTCHA"})
598+
599+
600+
def test_model_serializer(self):
601+
r = self.client.post(
602+
reverse("captcha-test-model-serializer"),
603+
dict(
604+
captcha_code=self.default_store.response,
605+
captcha_hashkey=self.default_store.hashkey,
606+
subject="xxx",
607+
608+
),
609+
)
610+
self.assertEqual(r.status_code, 200)
611+
612+
def test_wrong_model_serializer(self):
613+
r = self.client.post(
614+
reverse("captcha-test-model-serializer"),
615+
dict(
616+
captcha_code="xxx",
617+
captcha_hashkey="wrong hash",
618+
subject="xxx",
619+
620+
),
621+
)
622+
self.assertEqual(r.status_code, 400)
623+
self.assertEqual(json.loads(r.content), {"error": "Invalid CAPTCHA"})
624+
578625

579626
def trivial_challenge():
580627
return "trivial", "trivial"

captcha/tests/urls.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,16 @@
88
test_model_form,
99
test_non_required,
1010
test_per_form_format,
11+
test_serializer,
12+
test_model_serializer,
1113
)
1214

1315

1416
urlpatterns = [
1517
re_path(r"test/$", test, name="captcha-test"),
1618
re_path(r"test-modelform/$", test_model_form, name="captcha-test-model-form"),
19+
re_path(r"test-serializer/$", test_serializer, name="captcha-test-serializer"),
20+
re_path(r"test-model-serializer/$", test_model_serializer, name="captcha-test-model-serializer"),
1721
re_path(
1822
r"test2/$", test_custom_error_message, name="captcha-test-custom-error-message"
1923
),

captcha/tests/views.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@
22
from django.contrib.auth.models import User
33
from django.http import HttpResponse
44
from django.template import engines
5+
from rest_framework import status
6+
from rest_framework.decorators import api_view
7+
from rest_framework.response import Response
58

9+
from rest_framework import serializers
610
from captcha.fields import CaptchaField
7-
11+
from captcha.serializers import CaptchaSerializer, CaptchaModelSerializer
812

913
TEST_TEMPLATE = r"""
1014
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
@@ -126,3 +130,25 @@ class CaptchaTestForm(forms.Form):
126130
captcha2 = CaptchaField(id_prefix="form2")
127131

128132
return _test(request, CaptchaTestForm)
133+
134+
135+
@api_view(['POST'])
136+
def test_serializer(request):
137+
serializer = CaptchaSerializer(data=request.POST)
138+
serializer.is_valid(raise_exception=True)
139+
return Response(status=status.HTTP_200_OK)
140+
141+
142+
@api_view(['POST'])
143+
def test_model_serializer(request):
144+
class UserCaptchaModelSerializer(CaptchaModelSerializer):
145+
subject = serializers.CharField(max_length=100)
146+
sender = serializers.EmailField()
147+
148+
class Meta:
149+
model = User
150+
fields = ("subject", "sender", "captcha_code", "captcha_hashkey")
151+
152+
serializer = UserCaptchaModelSerializer(data=request.POST)
153+
serializer.is_valid(raise_exception=True)
154+
return Response(status=status.HTTP_200_OK)

captcha/validators.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from rest_framework import exceptions
2+
3+
import django
4+
from django.utils import timezone
5+
6+
7+
if django.VERSION >= (3, 0):
8+
from django.utils.translation import gettext_lazy as gettext_lazy
9+
else:
10+
from django.utils.translation import ugettext_lazy as gettext_lazy
11+
12+
from captcha.conf import settings
13+
from captcha.models import CaptchaStore
14+
15+
16+
def captcha_validate(hashkey, response):
17+
hashkey, response = hashkey.lower(), response.lower()
18+
19+
if not settings.CAPTCHA_GET_FROM_POOL:
20+
CaptchaStore.remove_expired()
21+
if settings.CAPTCHA_TEST_MODE and response.lower() == "passed":
22+
try:
23+
CaptchaStore.objects.get(hashkey=hashkey).delete()
24+
except CaptchaStore.DoesNotExist:
25+
pass
26+
else:
27+
try:
28+
CaptchaStore.objects.get(
29+
response=response, hashkey=hashkey, expiration__gt=timezone.now()
30+
).delete()
31+
except CaptchaStore.DoesNotExist:
32+
raise exceptions.ValidationError({"error": gettext_lazy("Invalid CAPTCHA")})

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ def run_tests(self):
3535
"Django >= 4.2",
3636
"Pillow >=6.2.0",
3737
"django-ranged-response == 0.2.0",
38+
"djangorestframework >= 3.15.0",
3839
]
3940
EXTRAS_REQUIRE = {"test": ("testfixtures",)}
4041

testproject/settings.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"django.contrib.sites",
3030
"django.forms",
3131
"django.contrib.messages",
32+
"rest_framework",
3233
"captcha",
3334
]
3435
FORM_RENDERER = "django.forms.renderers.TemplatesSetting"

tox.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ deps =
3030
py{39,310,311,312}-django{42,50}: python3-memcached
3131
jinja2
3232
Pillow
33+
djangorestframework
3334

3435
extras =
3536
test

0 commit comments

Comments
 (0)