prune_captcha 1.12.2__py3-none-any.whl → 1.14.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- captcha_prune/asgi.py +1 -10
- captcha_prune/utils.py +29 -21
- captcha_prune/wsgi.py +1 -10
- {prune_captcha-1.12.2.dist-info → prune_captcha-1.14.0.dist-info}/METADATA +5 -1
- prune_captcha-1.14.0.dist-info/RECORD +10 -0
- {prune_captcha-1.12.2.dist-info → prune_captcha-1.14.0.dist-info}/top_level.txt +0 -2
- captcha_prune/migrations/0001_initial.py +0 -29
- captcha_prune/migrations/0002_remove_captcha_created_at_remove_captcha_pos_x_and_more.py +0 -73
- captcha_prune/migrations/0003_captcha_created_at_captcha_updated_at.py +0 -25
- captcha_prune/migrations/__init__.py +0 -0
- captcha_prune/models.py +0 -34
- captcha_prune/payloads.py +0 -6
- commons/base_model.py +0 -9
- prune_captcha-1.12.2.dist-info/RECORD +0 -20
- tests/__init__.py +0 -0
- tests/captcha_prune/__init__.py +0 -0
- tests/captcha_prune/test_views.py +0 -67
- {prune_captcha-1.12.2.dist-info → prune_captcha-1.14.0.dist-info}/WHEEL +0 -0
captcha_prune/asgi.py
CHANGED
@@ -1,16 +1,7 @@
|
|
1
|
-
"""
|
2
|
-
ASGI config for captcha_prune project.
|
3
|
-
|
4
|
-
It exposes the ASGI callable as a module-level variable named ``application``.
|
5
|
-
|
6
|
-
For more information on this file, see
|
7
|
-
https://docs.djangoproject.com/en/5.2/howto/deployment/asgi/
|
8
|
-
"""
|
9
|
-
|
10
1
|
import os
|
11
2
|
|
12
3
|
from django.core.asgi import get_asgi_application
|
13
4
|
|
14
|
-
os.environ.setdefault(
|
5
|
+
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "captcha_prune.settings")
|
15
6
|
|
16
7
|
application = get_asgi_application()
|
captcha_prune/utils.py
CHANGED
@@ -2,14 +2,16 @@ import os
|
|
2
2
|
import random
|
3
3
|
|
4
4
|
from django.conf import settings
|
5
|
-
from django.
|
5
|
+
from django.http import HttpRequest
|
6
6
|
|
7
|
-
from captcha_prune.models import Captcha
|
8
|
-
|
9
|
-
|
10
|
-
def create_and_get_captcha() -> dict:
|
11
|
-
captcha = Captcha.objects.create()
|
12
7
|
|
8
|
+
def create_and_get_captcha(
|
9
|
+
request, *, width=350, height=200, piece_width=80, piece_height=50, precision=2
|
10
|
+
) -> dict:
|
11
|
+
pos_x_solution = random.randint(0, width - piece_width)
|
12
|
+
pos_y_solution = random.randint(0, height - piece_height)
|
13
|
+
piece_pos_x = random.randint(0, width - piece_width)
|
14
|
+
piece_pos_y = random.randint(0, height - piece_height)
|
13
15
|
_, _, puzzle_images_path = settings.PUZZLE_IMAGE_STATIC_PATH.rpartition("static/")
|
14
16
|
puzzle_images = [
|
15
17
|
f
|
@@ -17,27 +19,33 @@ def create_and_get_captcha() -> dict:
|
|
17
19
|
if f.lower().endswith((".jpg", ".jpeg", ".png", ".gif", ".webp"))
|
18
20
|
]
|
19
21
|
selected_image = random.choice(puzzle_images)
|
20
|
-
|
22
|
+
request.session["pos_x_solution"] = pos_x_solution
|
23
|
+
request.session["pos_y_solution"] = pos_y_solution
|
24
|
+
request.session["precision"] = precision
|
21
25
|
return {
|
22
|
-
"
|
23
|
-
"
|
24
|
-
"
|
25
|
-
"
|
26
|
-
"
|
27
|
-
"
|
28
|
-
"
|
29
|
-
"
|
30
|
-
"piece_pos_y": captcha.piece_pos_y,
|
26
|
+
"width": width,
|
27
|
+
"height": height,
|
28
|
+
"piece_width": piece_width,
|
29
|
+
"piece_height": piece_height,
|
30
|
+
"pos_x_solution": pos_x_solution,
|
31
|
+
"pos_y_solution": pos_y_solution,
|
32
|
+
"piece_pos_x": piece_pos_x,
|
33
|
+
"piece_pos_y": piece_pos_y,
|
31
34
|
"image": f"{puzzle_images_path}{selected_image}",
|
32
35
|
}
|
33
36
|
|
34
37
|
|
35
|
-
def verify_captcha(
|
36
|
-
|
37
|
-
|
38
|
+
def verify_captcha(request: HttpRequest) -> bool:
|
39
|
+
pos_x_answer = request.POST.get("pos_x_answer")
|
40
|
+
pos_y_answer = request.POST.get("pos_Y_answer")
|
41
|
+
if pos_x_answer is None or pos_y_answer is None:
|
42
|
+
return False
|
43
|
+
pos_x_solution = request.session.get("pos_x_solution")
|
44
|
+
pos_y_solution = request.session.get("pos_y_solution")
|
45
|
+
precision = request.session.get("precision")
|
38
46
|
if (
|
39
|
-
abs(
|
40
|
-
and abs(
|
47
|
+
abs(pos_x_solution - pos_x_answer) <= precision
|
48
|
+
and abs(pos_y_solution - pos_y_answer) <= precision
|
41
49
|
):
|
42
50
|
return True
|
43
51
|
else:
|
captcha_prune/wsgi.py
CHANGED
@@ -1,16 +1,7 @@
|
|
1
|
-
"""
|
2
|
-
WSGI config for captcha_prune project.
|
3
|
-
|
4
|
-
It exposes the WSGI callable as a module-level variable named ``application``.
|
5
|
-
|
6
|
-
For more information on this file, see
|
7
|
-
https://docs.djangoproject.com/en/5.2/howto/deployment/wsgi/
|
8
|
-
"""
|
9
|
-
|
10
1
|
import os
|
11
2
|
|
12
3
|
from django.core.wsgi import get_wsgi_application
|
13
4
|
|
14
|
-
os.environ.setdefault(
|
5
|
+
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "captcha_prune.settings")
|
15
6
|
|
16
7
|
application = get_wsgi_application()
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: prune_captcha
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.14.0
|
4
4
|
Summary: A tool to protect formulaire from spam.
|
5
5
|
Author-email: Arnout <bastien@prune.sh>
|
6
6
|
Project-URL: Made_by, https://prune.sh/
|
@@ -64,6 +64,8 @@ In `settings.py`, set the path to the images used for the puzzle:
|
|
64
64
|
PUZZLE_IMAGE_STATIC_PATH = "website/static/website/images/puzzles/"
|
65
65
|
```
|
66
66
|
|
67
|
+
Important: You must import the static files (css, js) present in "captcha_prune/static/".
|
68
|
+
|
67
69
|
### Utilisation
|
68
70
|
|
69
71
|
- GET request (form display)
|
@@ -114,6 +116,8 @@ PUZZLE_IMAGE_STATIC_PATH = "website/static/website/images/puzzles/"
|
|
114
116
|
|
115
117
|
| Version | Date | Notes |
|
116
118
|
| ------- | ---------- | ---------------------------------- |
|
119
|
+
| 1.14.0 | 2025-05-21 | fix static and templates dirs |
|
120
|
+
| 1.13.0 | 2025-05-21 | use session, add static |
|
117
121
|
| 1.12.2 | 2025-05-21 | doc fixed |
|
118
122
|
| 1.12.1 | 2025-05-21 | doc fixed |
|
119
123
|
| 1.12.0 | 2025-05-21 | removed views, added utils, ... |
|
@@ -0,0 +1,10 @@
|
|
1
|
+
captcha_prune/__init__.py,sha256=JerrqDuZCUa8pj_9pEv68rnlPsiXovRP4biKpCqyThs,61
|
2
|
+
captcha_prune/apps.py,sha256=WVAz3WWaPAgM7-Ojd_Cl2KVdcub1n-03qpnRyu2ToTo,422
|
3
|
+
captcha_prune/asgi.py,sha256=JoxjDwB6GttsGjXKPxEAR0-r8HAFgdg7QNQ36LQpbxw,174
|
4
|
+
captcha_prune/settings.py,sha256=YEZHEjYYYK-cH3gSLIR3a7g4Bypnuf_5eMTTcm719CE,3395
|
5
|
+
captcha_prune/utils.py,sha256=0wgRNCfmAAugFh5sw3qk-YqvNMiMZgJRtd6QlpRa-D8,1861
|
6
|
+
captcha_prune/wsgi.py,sha256=9Voquvmt99N0h_URABBvpZUpg9zcx3e5yLJ7M33xzWY,174
|
7
|
+
prune_captcha-1.14.0.dist-info/METADATA,sha256=_NhyAu6m1CN1PHLSA4d3RdBqzHcd--8W3Hx8O6L5Xi4,3769
|
8
|
+
prune_captcha-1.14.0.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
|
9
|
+
prune_captcha-1.14.0.dist-info/top_level.txt,sha256=01HnLL5Bu66yMBCvNpNBzN0L6AxTdG1_1YFSJr3dI2w,14
|
10
|
+
prune_captcha-1.14.0.dist-info/RECORD,,
|
@@ -1,29 +0,0 @@
|
|
1
|
-
# Generated by Django 5.2 on 2025-04-28 10:19
|
2
|
-
|
3
|
-
import uuid
|
4
|
-
from django.db import migrations, models
|
5
|
-
|
6
|
-
|
7
|
-
class Migration(migrations.Migration):
|
8
|
-
|
9
|
-
initial = True
|
10
|
-
|
11
|
-
dependencies = [
|
12
|
-
]
|
13
|
-
|
14
|
-
operations = [
|
15
|
-
migrations.CreateModel(
|
16
|
-
name='Captcha',
|
17
|
-
fields=[
|
18
|
-
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
19
|
-
('created_at', models.DateTimeField(auto_now_add=True)),
|
20
|
-
('updated_at', models.DateTimeField(auto_now=True)),
|
21
|
-
('uuid', models.UUIDField(default=uuid.uuid4, unique=True)),
|
22
|
-
('pos_x', models.IntegerField()),
|
23
|
-
('pos_y', models.IntegerField()),
|
24
|
-
],
|
25
|
-
options={
|
26
|
-
'abstract': False,
|
27
|
-
},
|
28
|
-
),
|
29
|
-
]
|
@@ -1,73 +0,0 @@
|
|
1
|
-
# Generated by Django 5.2 on 2025-04-29 07:55
|
2
|
-
|
3
|
-
from django.db import migrations, models
|
4
|
-
|
5
|
-
|
6
|
-
class Migration(migrations.Migration):
|
7
|
-
dependencies = [
|
8
|
-
("captcha_prune", "0001_initial"),
|
9
|
-
]
|
10
|
-
|
11
|
-
operations = [
|
12
|
-
migrations.RemoveField(
|
13
|
-
model_name="captcha",
|
14
|
-
name="created_at",
|
15
|
-
),
|
16
|
-
migrations.RemoveField(
|
17
|
-
model_name="captcha",
|
18
|
-
name="pos_x",
|
19
|
-
),
|
20
|
-
migrations.RemoveField(
|
21
|
-
model_name="captcha",
|
22
|
-
name="pos_y",
|
23
|
-
),
|
24
|
-
migrations.RemoveField(
|
25
|
-
model_name="captcha",
|
26
|
-
name="updated_at",
|
27
|
-
),
|
28
|
-
migrations.AddField(
|
29
|
-
model_name="captcha",
|
30
|
-
name="height",
|
31
|
-
field=models.IntegerField(default=200),
|
32
|
-
),
|
33
|
-
migrations.AddField(
|
34
|
-
model_name="captcha",
|
35
|
-
name="piece_height",
|
36
|
-
field=models.IntegerField(default=50),
|
37
|
-
),
|
38
|
-
migrations.AddField(
|
39
|
-
model_name="captcha",
|
40
|
-
name="piece_pos_x",
|
41
|
-
field=models.IntegerField(blank=True, null=True),
|
42
|
-
),
|
43
|
-
migrations.AddField(
|
44
|
-
model_name="captcha",
|
45
|
-
name="piece_pos_y",
|
46
|
-
field=models.IntegerField(blank=True, null=True),
|
47
|
-
),
|
48
|
-
migrations.AddField(
|
49
|
-
model_name="captcha",
|
50
|
-
name="piece_width",
|
51
|
-
field=models.IntegerField(default=80),
|
52
|
-
),
|
53
|
-
migrations.AddField(
|
54
|
-
model_name="captcha",
|
55
|
-
name="pos_x_solution",
|
56
|
-
field=models.IntegerField(blank=True, null=True),
|
57
|
-
),
|
58
|
-
migrations.AddField(
|
59
|
-
model_name="captcha",
|
60
|
-
name="pos_y_solution",
|
61
|
-
field=models.IntegerField(blank=True, null=True),
|
62
|
-
),
|
63
|
-
migrations.AddField(
|
64
|
-
model_name="captcha",
|
65
|
-
name="precision",
|
66
|
-
field=models.IntegerField(default=2),
|
67
|
-
),
|
68
|
-
migrations.AddField(
|
69
|
-
model_name="captcha",
|
70
|
-
name="width",
|
71
|
-
field=models.IntegerField(default=350),
|
72
|
-
),
|
73
|
-
]
|
@@ -1,25 +0,0 @@
|
|
1
|
-
# Generated by Django 5.2 on 2025-05-20 09:12
|
2
|
-
|
3
|
-
import django.utils.timezone
|
4
|
-
from django.db import migrations, models
|
5
|
-
|
6
|
-
|
7
|
-
class Migration(migrations.Migration):
|
8
|
-
|
9
|
-
dependencies = [
|
10
|
-
('captcha_prune', '0002_remove_captcha_created_at_remove_captcha_pos_x_and_more'),
|
11
|
-
]
|
12
|
-
|
13
|
-
operations = [
|
14
|
-
migrations.AddField(
|
15
|
-
model_name='captcha',
|
16
|
-
name='created_at',
|
17
|
-
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
|
18
|
-
preserve_default=False,
|
19
|
-
),
|
20
|
-
migrations.AddField(
|
21
|
-
model_name='captcha',
|
22
|
-
name='updated_at',
|
23
|
-
field=models.DateTimeField(auto_now=True),
|
24
|
-
),
|
25
|
-
]
|
File without changes
|
captcha_prune/models.py
DELETED
@@ -1,34 +0,0 @@
|
|
1
|
-
import random
|
2
|
-
import uuid
|
3
|
-
|
4
|
-
from django.db import models
|
5
|
-
|
6
|
-
from commons.base_model import BaseModel
|
7
|
-
|
8
|
-
|
9
|
-
class Captcha(BaseModel):
|
10
|
-
uuid = models.UUIDField(default=uuid.uuid4, unique=True)
|
11
|
-
width = models.IntegerField(default=350)
|
12
|
-
height = models.IntegerField(default=200)
|
13
|
-
pos_x_solution = models.IntegerField(null=True, blank=True)
|
14
|
-
pos_y_solution = models.IntegerField(null=True, blank=True)
|
15
|
-
piece_pos_x = models.IntegerField(null=True, blank=True)
|
16
|
-
piece_pos_y = models.IntegerField(null=True, blank=True)
|
17
|
-
piece_width = models.IntegerField(default=80)
|
18
|
-
piece_height = models.IntegerField(default=50)
|
19
|
-
precision = models.IntegerField(default=2)
|
20
|
-
|
21
|
-
def save(self, *args, **kwargs):
|
22
|
-
if self.pos_x_solution is None:
|
23
|
-
self.pos_x_solution = random.randint(0, self.width - self.piece_width)
|
24
|
-
|
25
|
-
if self.pos_y_solution is None:
|
26
|
-
self.pos_y_solution = random.randint(0, self.height - self.piece_height)
|
27
|
-
|
28
|
-
if self.piece_pos_x is None:
|
29
|
-
self.piece_pos_x = random.randint(0, self.width - self.piece_width)
|
30
|
-
|
31
|
-
if self.piece_pos_y is None:
|
32
|
-
self.piece_pos_y = random.randint(0, self.height - self.piece_height)
|
33
|
-
|
34
|
-
super().save(*args, **kwargs)
|
captcha_prune/payloads.py
DELETED
commons/base_model.py
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
captcha_prune/__init__.py,sha256=JerrqDuZCUa8pj_9pEv68rnlPsiXovRP4biKpCqyThs,61
|
2
|
-
captcha_prune/apps.py,sha256=WVAz3WWaPAgM7-Ojd_Cl2KVdcub1n-03qpnRyu2ToTo,422
|
3
|
-
captcha_prune/asgi.py,sha256=wi2rRLFltXOZyve8mAB_E8udaFyONOf5N42WrWIQX8M,403
|
4
|
-
captcha_prune/models.py,sha256=O8nxhVfPat3oaDGZzd88kQHkGayQIWZ_dOO9uu1R6P4,1240
|
5
|
-
captcha_prune/payloads.py,sha256=PwR48xKg9YEgGgoJmGe7ywwFPCqM4gumomyFunn-Ems,115
|
6
|
-
captcha_prune/settings.py,sha256=YEZHEjYYYK-cH3gSLIR3a7g4Bypnuf_5eMTTcm719CE,3395
|
7
|
-
captcha_prune/utils.py,sha256=bEOMAA4jatS9eAgHEHpq0qNHjkCrywDGHAn81JPwbuo,1352
|
8
|
-
captcha_prune/wsgi.py,sha256=fukA_iiCT4FFzcJ0QX2Zr_HNddqdq-ln3ien2UWcCTA,403
|
9
|
-
captcha_prune/migrations/0001_initial.py,sha256=QMTaeSIxPociSrV4r89zfsbxMwMu4qpL16LT3yPgcaw,831
|
10
|
-
captcha_prune/migrations/0002_remove_captcha_created_at_remove_captcha_pos_x_and_more.py,sha256=xyyE_G3t8kJqFvKuNWFRTyTyyD1kzvfeSgbkTQ9DO8A,2099
|
11
|
-
captcha_prune/migrations/0003_captcha_created_at_captcha_updated_at.py,sha256=qT9cTDe3coiWXYbYfBlK4iEYg71E9B2FGuo74Wy_gbA,698
|
12
|
-
captcha_prune/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
13
|
-
commons/base_model.py,sha256=q1Q7lgtvN_t6ujhEiSsNNXb_ay0qY6FbfK0Zm2Hdgp0,213
|
14
|
-
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
15
|
-
tests/captcha_prune/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
16
|
-
tests/captcha_prune/test_views.py,sha256=h-Mrdiprh71s7kIJPYAt6xv4G_S7vp51WCpAzy4CZ6U,2364
|
17
|
-
prune_captcha-1.12.2.dist-info/METADATA,sha256=au5j11PrwDHhnBX3aEsK5n1kd8Lli0SO3tX9NGbFU-4,3554
|
18
|
-
prune_captcha-1.12.2.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
|
19
|
-
prune_captcha-1.12.2.dist-info/top_level.txt,sha256=EB4h0WF_YGF7kAWB0XtqmuloOhkL5pC71CzgEVYam7w,28
|
20
|
-
prune_captcha-1.12.2.dist-info/RECORD,,
|
tests/__init__.py
DELETED
File without changes
|
tests/captcha_prune/__init__.py
DELETED
File without changes
|
@@ -1,67 +0,0 @@
|
|
1
|
-
import uuid
|
2
|
-
|
3
|
-
from django.test import TestCase
|
4
|
-
from django.urls import reverse
|
5
|
-
|
6
|
-
from captcha_prune.models import Captcha
|
7
|
-
|
8
|
-
|
9
|
-
class CreateCaptchaViewTestCase(TestCase):
|
10
|
-
def test_create_captcha_with_GET_method(self):
|
11
|
-
response = self.client.get(reverse("create-captcha"))
|
12
|
-
self.assertEqual(response.status_code, 405)
|
13
|
-
|
14
|
-
def test_create_captcha_with_POST_method(self):
|
15
|
-
response = self.client.post(reverse("create-captcha"))
|
16
|
-
self.assertEqual(response.status_code, 201)
|
17
|
-
|
18
|
-
data = response.json()
|
19
|
-
captcha = Captcha.objects.get(uuid=data["uuid"])
|
20
|
-
self.assertIsNotNone(captcha)
|
21
|
-
|
22
|
-
|
23
|
-
class VerifyCaptchaViewTestCase(TestCase):
|
24
|
-
def setUp(self):
|
25
|
-
self.captcha = Captcha.objects.create()
|
26
|
-
self.pos_x_solution = self.captcha.pos_x_solution
|
27
|
-
self.pos_y_solution = self.captcha.pos_y_solution
|
28
|
-
|
29
|
-
def test_verify_captcha_with_POST_method(self):
|
30
|
-
response = self.client.post(
|
31
|
-
reverse("verify-captcha", kwargs={"uuid": str(uuid.uuid4())}),
|
32
|
-
data={
|
33
|
-
"pos_x_answer": self.pos_x_solution,
|
34
|
-
"pos_y_answer": self.pos_y_solution,
|
35
|
-
},
|
36
|
-
)
|
37
|
-
self.assertEqual(response.status_code, 405)
|
38
|
-
|
39
|
-
def test_verify_captcha_with_bad_uuid(self):
|
40
|
-
response = self.client.get(
|
41
|
-
reverse("verify-captcha", kwargs={"uuid": str(uuid.uuid4())}),
|
42
|
-
data={
|
43
|
-
"pos_x_answer": self.pos_x_solution,
|
44
|
-
"pos_y_answer": self.pos_y_solution,
|
45
|
-
},
|
46
|
-
)
|
47
|
-
self.assertEqual(response.status_code, 404)
|
48
|
-
|
49
|
-
def test_verify_captcha_with_good_uuid_but_bad_answer(self):
|
50
|
-
response = self.client.get(
|
51
|
-
reverse("verify-captcha", kwargs={"uuid": str(self.captcha.uuid)}),
|
52
|
-
data={
|
53
|
-
"pos_x_answer": self.pos_x_solution - 20,
|
54
|
-
"pos_y_answer": self.pos_y_solution - 20,
|
55
|
-
},
|
56
|
-
)
|
57
|
-
self.assertEqual(response.status_code, 401)
|
58
|
-
|
59
|
-
def test_verify_captcha_with_good_uuid_and_good_answer(self):
|
60
|
-
response = self.client.get(
|
61
|
-
reverse("verify-captcha", kwargs={"uuid": str(self.captcha.uuid)}),
|
62
|
-
data={
|
63
|
-
"pos_x_answer": self.pos_x_solution,
|
64
|
-
"pos_y_answer": self.pos_y_solution,
|
65
|
-
},
|
66
|
-
)
|
67
|
-
self.assertEqual(response.status_code, 304)
|
File without changes
|