prune_captcha 1.9.0__py3-none-any.whl → 1.11.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/urls.py +2 -2
- captcha_prune/views.py +40 -22
- {prune_captcha-1.9.0.dist-info → prune_captcha-1.11.0.dist-info}/METADATA +40 -32
- {prune_captcha-1.9.0.dist-info → prune_captcha-1.11.0.dist-info}/RECORD +6 -8
- {prune_captcha-1.9.0.dist-info → prune_captcha-1.11.0.dist-info}/WHEEL +1 -1
- captcha_prune/context_processors.py +0 -12
- captcha_prune/utils.py +0 -68
- {prune_captcha-1.9.0.dist-info → prune_captcha-1.11.0.dist-info}/top_level.txt +0 -0
captcha_prune/urls.py
CHANGED
@@ -5,6 +5,6 @@ from captcha_prune.views import create_captcha_view, verify_captcha_view
|
|
5
5
|
app_name = "captcha"
|
6
6
|
|
7
7
|
urlpatterns = [
|
8
|
-
path("", create_captcha_view, name="create-captcha"),
|
9
|
-
path("
|
8
|
+
path("create/", create_captcha_view, name="create-captcha"),
|
9
|
+
path("verify/", verify_captcha_view, name="verify-captcha"),
|
10
10
|
]
|
captcha_prune/views.py
CHANGED
@@ -1,4 +1,9 @@
|
|
1
|
-
|
1
|
+
import os
|
2
|
+
import random
|
3
|
+
|
4
|
+
from django.conf import settings
|
5
|
+
from django.contrib import messages
|
6
|
+
from django.http import HttpRequest
|
2
7
|
from django.shortcuts import get_object_or_404
|
3
8
|
from django.views.decorators.csrf import csrf_exempt
|
4
9
|
from django.views.decorators.http import require_GET, require_POST
|
@@ -10,36 +15,49 @@ from commons.decorators import use_payload
|
|
10
15
|
|
11
16
|
@require_POST
|
12
17
|
@csrf_exempt
|
13
|
-
def create_captcha_view(request: HttpRequest) ->
|
18
|
+
def create_captcha_view(request: HttpRequest) -> dict:
|
14
19
|
captcha = Captcha.objects.create()
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
20
|
+
request.session["puzzle_uuid"] = captcha.uuid
|
21
|
+
|
22
|
+
_, _, puzzle_images_path = settings.PUZZLE_IMAGE_STATIC_PATH.rpartition("static/")
|
23
|
+
puzzle_images = [
|
24
|
+
f
|
25
|
+
for f in os.listdir(settings.PUZZLE_IMAGE_STATIC_PATH)
|
26
|
+
if f.lower().endswith((".jpg", ".jpeg", ".png", ".gif", ".webp"))
|
27
|
+
]
|
28
|
+
selected_image = random.choice(puzzle_images)
|
29
|
+
|
30
|
+
return {
|
31
|
+
"uuid": captcha.uuid,
|
32
|
+
"width": captcha.width,
|
33
|
+
"height": captcha.height,
|
34
|
+
"piece_width": captcha.piece_width,
|
35
|
+
"piece_height": captcha.piece_height,
|
36
|
+
"pos_x_solution": captcha.pos_x_solution,
|
37
|
+
"pos_y_solution": captcha.pos_y_solution,
|
38
|
+
"piece_pos_x": captcha.piece_pos_x,
|
39
|
+
"piece_pos_y": captcha.piece_pos_y,
|
40
|
+
"image": f"{puzzle_images_path}{selected_image}",
|
41
|
+
}
|
29
42
|
|
30
43
|
|
31
44
|
@require_GET
|
32
45
|
@use_payload(PuzzleAnswerPayload)
|
33
|
-
def verify_captcha_view(
|
34
|
-
request: HttpRequest, uuid: str, payload: PuzzleAnswerPayload
|
35
|
-
) -> HttpResponse:
|
36
|
-
captcha = get_object_or_404(Captcha, uuid=uuid)
|
46
|
+
def verify_captcha_view(request: HttpRequest, payload: PuzzleAnswerPayload) -> bool:
|
37
47
|
pos_x_answer = payload.pos_x_answer
|
38
48
|
pos_y_answer = payload.pos_y_answer
|
49
|
+
|
50
|
+
puzzle_uuid = request.session.get("puzzle_uuid")
|
51
|
+
if not puzzle_uuid:
|
52
|
+
messages.error(request, "La session a expiré.")
|
53
|
+
return False
|
54
|
+
captcha = get_object_or_404(Captcha, uuid=puzzle_uuid)
|
55
|
+
|
39
56
|
if (
|
40
57
|
abs(captcha.pos_x_solution - pos_x_answer) <= captcha.precision
|
41
58
|
and abs(captcha.pos_y_solution - pos_y_answer) <= captcha.precision
|
42
59
|
):
|
43
|
-
return
|
60
|
+
return True
|
44
61
|
else:
|
45
|
-
|
62
|
+
messages.error(request, "Captcha incorrect. Veuillez réessayer.")
|
63
|
+
return False
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: prune_captcha
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.11.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/
|
@@ -61,81 +61,90 @@ Don't hesitate to regularly run `poetry update`, as the captcha evolves with tim
|
|
61
61
|
In `settings.py`, set the path to the images used for the puzzle:
|
62
62
|
|
63
63
|
```python
|
64
|
-
PUZZLE_IMAGE_STATIC_PATH = "website/images/"
|
64
|
+
PUZZLE_IMAGE_STATIC_PATH = "website/static/website/images/"
|
65
65
|
```
|
66
66
|
|
67
67
|
### Utilisation
|
68
68
|
|
69
69
|
- GET request (form display)
|
70
70
|
|
71
|
-
- Use
|
71
|
+
- Use create_captcha_view to generate the captcha data:
|
72
72
|
|
73
|
-
|
73
|
+
```python
|
74
|
+
from captcha_prune.views import create_captcha_view
|
75
|
+
```
|
74
76
|
|
75
|
-
|
77
|
+
```python
|
78
|
+
puzzle = create_captcha_view(request)
|
79
|
+
```
|
80
|
+
|
81
|
+
- Passes the data into the context under the puzzle variable:
|
76
82
|
|
77
83
|
```python
|
78
|
-
|
84
|
+
return render(
|
85
|
+
request,
|
86
|
+
"website/pages/contact/page.html",
|
87
|
+
{"form": form, "puzzle": puzzle},
|
88
|
+
)
|
79
89
|
```
|
80
90
|
|
91
|
+
- Include the component in your template:
|
92
|
+
|
81
93
|
```
|
82
94
|
{% include "captcha_prune/captcha.html" %}
|
83
95
|
```
|
84
96
|
|
85
97
|
- POST request (form submission)
|
86
98
|
|
87
|
-
- Use
|
99
|
+
- Use verify_captcha_view to validate the captcha:
|
88
100
|
|
89
101
|
```python
|
90
|
-
from captcha_prune.
|
102
|
+
from captcha_prune.views import verify_captcha_view
|
91
103
|
```
|
92
104
|
|
93
105
|
```python
|
94
|
-
|
106
|
+
response = verify_captcha_view(request)
|
95
107
|
```
|
96
108
|
|
97
|
-
-
|
109
|
+
- True if the captcha is correct, else False.
|
98
110
|
|
99
|
-
- Redirects in case of expired session or incorrect captcha
|
111
|
+
- Redirects in case of expired session or incorrect captcha:
|
112
|
+
|
113
|
+
```python
|
114
|
+
if response is False:
|
115
|
+
return redirect("website:contact-page")
|
116
|
+
```
|
100
117
|
|
101
118
|
### Example
|
102
119
|
|
103
120
|
```python
|
104
|
-
from django.shortcuts import render, redirect
|
105
|
-
|
106
|
-
from django.contrib import messages
|
107
|
-
from captcha_prune.utils import create_and_get_puzzle, verify_captcha
|
108
|
-
from .forms import ContactForm
|
109
|
-
|
110
121
|
def contact_view(request):
|
111
122
|
if request.method == "POST":
|
112
123
|
form = ContactForm(request.POST)
|
113
124
|
if form.is_valid():
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
)
|
120
|
-
messages.success(request, "Formulaire soumis avec succès.")
|
121
|
-
return redirect("/")
|
122
|
-
else:
|
123
|
-
puzzle = create_and_get_puzzle(request)
|
125
|
+
response = verify_captcha_view(request)
|
126
|
+
if response is False:
|
127
|
+
return redirect("website:contact-page")
|
128
|
+
messages.success(request, "Formulaire soumis avec succès.")
|
129
|
+
return redirect("/")
|
124
130
|
else:
|
125
|
-
|
126
|
-
|
131
|
+
puzzle = create_captcha_view(request)
|
132
|
+
else:
|
133
|
+
form = ContactForm()
|
134
|
+
puzzle = create_captcha_view(request)
|
127
135
|
return render(
|
128
136
|
request,
|
129
137
|
"website/pages/contact/page.html",
|
130
138
|
{"form": form, "puzzle": puzzle},
|
131
139
|
)
|
132
|
-
|
133
|
-
```
|
140
|
+
```
|
134
141
|
|
135
142
|
# Available Versions
|
136
143
|
|
137
144
|
| Version | Date | Notes |
|
138
145
|
| ------- | ---------- | ---------------------------------- |
|
146
|
+
| 1.11.0 | 2025-05-21 | removed utils |
|
147
|
+
| 1.10.0 | 2025-05-20 | fix documentation, removed ... |
|
139
148
|
| 1.9.0 | 2025-05-20 | puzzle images path fixed |
|
140
149
|
| 1.8.0 | 2025-05-20 | added migrations |
|
141
150
|
| 1.7.0 | 2025-05-20 | PUZZLE_IMAGE_STATIC_PATH ... |
|
@@ -146,4 +155,3 @@ PUZZLE_IMAGE_STATIC_PATH = "website/images/"
|
|
146
155
|
| 1.2.0 | 2025-04-30 | fixed prune_captcha command, ... |
|
147
156
|
| 1.1.0 | 2025-04-30 | start_server was not a module, ... |
|
148
157
|
| 1.0.0 | 2025-04-29 | First version of the `captcha` ... |
|
149
|
-
```
|
@@ -1,13 +1,11 @@
|
|
1
1
|
captcha_prune/__init__.py,sha256=JerrqDuZCUa8pj_9pEv68rnlPsiXovRP4biKpCqyThs,61
|
2
2
|
captcha_prune/apps.py,sha256=WVAz3WWaPAgM7-Ojd_Cl2KVdcub1n-03qpnRyu2ToTo,422
|
3
3
|
captcha_prune/asgi.py,sha256=wi2rRLFltXOZyve8mAB_E8udaFyONOf5N42WrWIQX8M,403
|
4
|
-
captcha_prune/context_processors.py,sha256=RTHgIxoje5n2wr1YvefbgUlvwNfiCe7Eyz9VKVoN4ZA,383
|
5
4
|
captcha_prune/models.py,sha256=O8nxhVfPat3oaDGZzd88kQHkGayQIWZ_dOO9uu1R6P4,1240
|
6
5
|
captcha_prune/payloads.py,sha256=PwR48xKg9YEgGgoJmGe7ywwFPCqM4gumomyFunn-Ems,115
|
7
6
|
captcha_prune/settings.py,sha256=YEZHEjYYYK-cH3gSLIR3a7g4Bypnuf_5eMTTcm719CE,3395
|
8
|
-
captcha_prune/urls.py,sha256=
|
9
|
-
captcha_prune/
|
10
|
-
captcha_prune/views.py,sha256=BzjQfXLwYx2YSWODrnGpokqU8XeC-9QORivzWKxYofY,1539
|
7
|
+
captcha_prune/urls.py,sha256=0G1O0CO53iBW_QT5vlvyN1UzsWxxZiiooqApx6f3MeU,274
|
8
|
+
captcha_prune/views.py,sha256=Y4F3zhq5uYpPuHs-0eI8dpn_-02Vbqw-JURK3fTE7tg,2095
|
11
9
|
captcha_prune/wsgi.py,sha256=fukA_iiCT4FFzcJ0QX2Zr_HNddqdq-ln3ien2UWcCTA,403
|
12
10
|
captcha_prune/migrations/0001_initial.py,sha256=QMTaeSIxPociSrV4r89zfsbxMwMu4qpL16LT3yPgcaw,831
|
13
11
|
captcha_prune/migrations/0002_remove_captcha_created_at_remove_captcha_pos_x_and_more.py,sha256=xyyE_G3t8kJqFvKuNWFRTyTyyD1kzvfeSgbkTQ9DO8A,2099
|
@@ -18,7 +16,7 @@ commons/decorators.py,sha256=nU_3SyxENcWDdxo03VyNYEvoevplC6ig7BFogY8apNs,1306
|
|
18
16
|
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
19
17
|
tests/captcha_prune/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
20
18
|
tests/captcha_prune/test_views.py,sha256=h-Mrdiprh71s7kIJPYAt6xv4G_S7vp51WCpAzy4CZ6U,2364
|
21
|
-
prune_captcha-1.
|
22
|
-
prune_captcha-1.
|
23
|
-
prune_captcha-1.
|
24
|
-
prune_captcha-1.
|
19
|
+
prune_captcha-1.11.0.dist-info/METADATA,sha256=moZ4qiAgMV2a0wkTqUj8EPkISZgck95t6n_hvE6cVFQ,4259
|
20
|
+
prune_captcha-1.11.0.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
|
21
|
+
prune_captcha-1.11.0.dist-info/top_level.txt,sha256=EB4h0WF_YGF7kAWB0XtqmuloOhkL5pC71CzgEVYam7w,28
|
22
|
+
prune_captcha-1.11.0.dist-info/RECORD,,
|
@@ -1,12 +0,0 @@
|
|
1
|
-
from django.conf import settings
|
2
|
-
from django.core.exceptions import ImproperlyConfigured
|
3
|
-
|
4
|
-
|
5
|
-
def puzzle_static_path(request):
|
6
|
-
try:
|
7
|
-
path = settings.PUZZLE_IMAGE_STATIC_PATH
|
8
|
-
except AttributeError:
|
9
|
-
raise ImproperlyConfigured(
|
10
|
-
"Vous devez définir PUZZLE_IMAGE_STATIC_PATH dans votre settings.py"
|
11
|
-
)
|
12
|
-
return {"PUZZLE_IMAGE_STATIC_PATH": path}
|
captcha_prune/utils.py
DELETED
@@ -1,68 +0,0 @@
|
|
1
|
-
import os
|
2
|
-
import random
|
3
|
-
from urllib.parse import urlencode
|
4
|
-
|
5
|
-
import requests
|
6
|
-
from django.conf import settings
|
7
|
-
from django.contrib import messages
|
8
|
-
from django.http import HttpRequest, HttpResponse
|
9
|
-
from django.urls import reverse
|
10
|
-
|
11
|
-
|
12
|
-
def create_and_get_puzzle(request: HttpRequest) -> HttpResponse | dict:
|
13
|
-
_, _, puzzle_images_path = settings.PUZZLE_IMAGE_STATIC_PATH.rpartition("static/")
|
14
|
-
try:
|
15
|
-
response = requests.post(
|
16
|
-
request.build_absolute_uri(reverse("captcha:create-captcha"))
|
17
|
-
).json()
|
18
|
-
except requests.RequestException:
|
19
|
-
return HttpResponse(status=502)
|
20
|
-
request.session["puzzle_uuid"] = response["uuid"]
|
21
|
-
puzzle_images = [
|
22
|
-
f
|
23
|
-
for f in os.listdir(settings.PUZZLE_IMAGE_STATIC_PATH)
|
24
|
-
if f.lower().endswith((".jpg", ".jpeg", ".png", ".gif"))
|
25
|
-
]
|
26
|
-
selected_image = random.choice(puzzle_images)
|
27
|
-
return {
|
28
|
-
"uuid": response["uuid"],
|
29
|
-
"width": response["width"],
|
30
|
-
"height": response["height"],
|
31
|
-
"piece_width": response["piece_width"],
|
32
|
-
"piece_height": response["piece_height"],
|
33
|
-
"pos_x_solution": response["pos_x_solution"],
|
34
|
-
"pos_y_solution": response["pos_y_solution"],
|
35
|
-
"piece_pos_x": response["piece_pos_x"],
|
36
|
-
"piece_pos_y": response["piece_pos_y"],
|
37
|
-
"image": f"{puzzle_images_path}{selected_image}",
|
38
|
-
}
|
39
|
-
|
40
|
-
|
41
|
-
def verify_captcha(
|
42
|
-
request: HttpRequest,
|
43
|
-
session_expired: HttpResponse,
|
44
|
-
incorrect_captcha: HttpResponse,
|
45
|
-
form,
|
46
|
-
) -> HttpResponse | None:
|
47
|
-
puzzle_uuid = request.session.get("puzzle_uuid")
|
48
|
-
if not puzzle_uuid:
|
49
|
-
messages.error(request, "La session a expiré.")
|
50
|
-
return session_expired
|
51
|
-
try:
|
52
|
-
base_url = request.build_absolute_uri(
|
53
|
-
reverse("captcha:verify-captcha", kwargs={"uuid": puzzle_uuid}),
|
54
|
-
)
|
55
|
-
data = {
|
56
|
-
"pos_x_answer": request.POST.get("pos_x_answer"),
|
57
|
-
"pos_y_answer": request.POST.get("pos_y_answer"),
|
58
|
-
}
|
59
|
-
query_string = urlencode(data)
|
60
|
-
full_url = f"{base_url}?{query_string}"
|
61
|
-
response = requests.get(full_url)
|
62
|
-
if response.status_code == 401:
|
63
|
-
messages.error(request, "Captcha incorrect. Veuillez réessayer.")
|
64
|
-
return incorrect_captcha
|
65
|
-
del form.fields["pos_x_answer"]
|
66
|
-
del form.fields["pos_y_answer"]
|
67
|
-
except requests.RequestException:
|
68
|
-
return HttpResponse(status=502)
|
File without changes
|