prune_captcha 1.10.0__py3-none-any.whl → 1.12.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/utils.py CHANGED
@@ -1,68 +1,44 @@
1
1
  import os
2
2
  import random
3
- from urllib.parse import urlencode
4
3
 
5
- import requests
6
4
  from django.conf import settings
7
- from django.contrib import messages
8
- from django.http import HttpRequest, HttpResponse
9
- from django.urls import reverse
5
+ from django.shortcuts import get_object_or_404
10
6
 
7
+ from captcha_prune.models import Captcha
8
+
9
+
10
+ def create_and_get_captcha() -> dict:
11
+ captcha = Captcha.objects.create()
11
12
 
12
- def create_and_get_puzzle(request: HttpRequest) -> HttpResponse | dict:
13
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
14
  puzzle_images = [
22
15
  f
23
16
  for f in os.listdir(settings.PUZZLE_IMAGE_STATIC_PATH)
24
- if f.lower().endswith((".jpg", ".jpeg", ".png", ".gif"))
17
+ if f.lower().endswith((".jpg", ".jpeg", ".png", ".gif", ".webp"))
25
18
  ]
26
19
  selected_image = random.choice(puzzle_images)
20
+
27
21
  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"],
22
+ "uuid": captcha.uuid,
23
+ "width": captcha.width,
24
+ "height": captcha.height,
25
+ "piece_width": captcha.piece_width,
26
+ "piece_height": captcha.piece_height,
27
+ "pos_x_solution": captcha.pos_x_solution,
28
+ "pos_y_solution": captcha.pos_y_solution,
29
+ "piece_pos_x": captcha.piece_pos_x,
30
+ "piece_pos_y": captcha.piece_pos_y,
37
31
  "image": f"{puzzle_images_path}{selected_image}",
38
32
  }
39
33
 
40
34
 
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)
35
+ def verify_captcha(puzzle_uuid: str, pos_x_answer: int, pos_y_answer: int) -> bool:
36
+ captcha = get_object_or_404(Captcha, uuid=puzzle_uuid)
37
+
38
+ if (
39
+ abs(captcha.pos_x_solution - pos_x_answer) <= captcha.precision
40
+ and abs(captcha.pos_y_solution - pos_y_answer) <= captcha.precision
41
+ ):
42
+ return True
43
+ else:
44
+ return False
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: prune_captcha
3
- Version: 1.10.0
3
+ Version: 1.12.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,21 +61,21 @@ 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/static/website/images/"
64
+ PUZZLE_IMAGE_STATIC_PATH = "website/static/website/images/puzzles/"
65
65
  ```
66
66
 
67
67
  ### Utilisation
68
68
 
69
69
  - GET request (form display)
70
70
 
71
- - Use create_and_get_puzzle to generate the captcha data:
71
+ - Use create_captcha to generate the captcha data:
72
72
 
73
73
  ```python
74
- from captcha_prune.utils import create_and_get_puzzle
74
+ from captcha_prune.utils import create_captcha
75
75
  ```
76
76
 
77
77
  ```python
78
- puzzle = create_and_get_puzzle(request)
78
+ puzzle = create_captcha(request)
79
79
  ```
80
80
 
81
81
  - Passes the data into the context under the puzzle variable:
@@ -103,50 +103,19 @@ PUZZLE_IMAGE_STATIC_PATH = "website/static/website/images/"
103
103
  ```
104
104
 
105
105
  ```python
106
- response = verify_captcha(
107
- request, redirect("/"), redirect("website:contact-page"), form
108
- )
106
+ response = verify_captcha(request)
109
107
  ```
110
108
 
111
- - None if the captcha is correct.
109
+ - True if the captcha is correct, else False.
112
110
 
113
- - Redirects in case of expired session or incorrect captcha:
114
-
115
- ```python
116
- if response is not None:
117
- return response
118
- ```
119
-
120
- ### Example
121
-
122
- ```python
123
- def contact_view(request):
124
- if request.method == "POST":
125
- form = ContactForm(request.POST)
126
- if form.is_valid():
127
- response = verify_captcha(
128
- request, redirect("/"), redirect("website:contact-page"), form
129
- )
130
- if response is not None:
131
- return response
132
- messages.success(request, "Formulaire soumis avec succès.")
133
- return redirect("/")
134
- else:
135
- puzzle = create_and_get_puzzle(request)
136
- else:
137
- form = ContactForm()
138
- puzzle = create_and_get_puzzle(request)
139
- return render(
140
- request,
141
- "website/pages/contact/page.html",
142
- {"form": form, "puzzle": puzzle},
143
- )
144
- ```
111
+ - Redirects in case of incorrect captcha.
145
112
 
146
113
  # Available Versions
147
114
 
148
115
  | Version | Date | Notes |
149
116
  | ------- | ---------- | ---------------------------------- |
117
+ | 1.12.0 | 2025-05-21 | removed views, added utils, ... |
118
+ | 1.11.0 | 2025-05-21 | removed utils |
150
119
  | 1.10.0 | 2025-05-20 | fix documentation, removed ... |
151
120
  | 1.9.0 | 2025-05-20 | puzzle images path fixed |
152
121
  | 1.8.0 | 2025-05-20 | added migrations |
@@ -4,20 +4,17 @@ captcha_prune/asgi.py,sha256=wi2rRLFltXOZyve8mAB_E8udaFyONOf5N42WrWIQX8M,403
4
4
  captcha_prune/models.py,sha256=O8nxhVfPat3oaDGZzd88kQHkGayQIWZ_dOO9uu1R6P4,1240
5
5
  captcha_prune/payloads.py,sha256=PwR48xKg9YEgGgoJmGe7ywwFPCqM4gumomyFunn-Ems,115
6
6
  captcha_prune/settings.py,sha256=YEZHEjYYYK-cH3gSLIR3a7g4Bypnuf_5eMTTcm719CE,3395
7
- captcha_prune/urls.py,sha256=DeCJTjJA0krW27KoHafvh3GBRr7fJ80E2lVHEWYSsW4,271
8
- captcha_prune/utils.py,sha256=3AYKlLxLUdGf9SBGg5Mgzym7LlxBpDhYv4ITFFZ0nAc,2415
9
- captcha_prune/views.py,sha256=BzjQfXLwYx2YSWODrnGpokqU8XeC-9QORivzWKxYofY,1539
7
+ captcha_prune/utils.py,sha256=bEOMAA4jatS9eAgHEHpq0qNHjkCrywDGHAn81JPwbuo,1352
10
8
  captcha_prune/wsgi.py,sha256=fukA_iiCT4FFzcJ0QX2Zr_HNddqdq-ln3ien2UWcCTA,403
11
9
  captcha_prune/migrations/0001_initial.py,sha256=QMTaeSIxPociSrV4r89zfsbxMwMu4qpL16LT3yPgcaw,831
12
10
  captcha_prune/migrations/0002_remove_captcha_created_at_remove_captcha_pos_x_and_more.py,sha256=xyyE_G3t8kJqFvKuNWFRTyTyyD1kzvfeSgbkTQ9DO8A,2099
13
11
  captcha_prune/migrations/0003_captcha_created_at_captcha_updated_at.py,sha256=qT9cTDe3coiWXYbYfBlK4iEYg71E9B2FGuo74Wy_gbA,698
14
12
  captcha_prune/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
13
  commons/base_model.py,sha256=q1Q7lgtvN_t6ujhEiSsNNXb_ay0qY6FbfK0Zm2Hdgp0,213
16
- commons/decorators.py,sha256=nU_3SyxENcWDdxo03VyNYEvoevplC6ig7BFogY8apNs,1306
17
14
  tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
15
  tests/captcha_prune/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
16
  tests/captcha_prune/test_views.py,sha256=h-Mrdiprh71s7kIJPYAt6xv4G_S7vp51WCpAzy4CZ6U,2364
20
- prune_captcha-1.10.0.dist-info/METADATA,sha256=DVW1Kulk4o16TNlXHBkAxovoGG2XbyS2G9ywg4P-34g,4311
21
- prune_captcha-1.10.0.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
22
- prune_captcha-1.10.0.dist-info/top_level.txt,sha256=EB4h0WF_YGF7kAWB0XtqmuloOhkL5pC71CzgEVYam7w,28
23
- prune_captcha-1.10.0.dist-info/RECORD,,
17
+ prune_captcha-1.12.0.dist-info/METADATA,sha256=sU70ylcO8ntgm7y1CZqPQiEmwI7Yf-rXULvoZxn10Dw,3443
18
+ prune_captcha-1.12.0.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
19
+ prune_captcha-1.12.0.dist-info/top_level.txt,sha256=EB4h0WF_YGF7kAWB0XtqmuloOhkL5pC71CzgEVYam7w,28
20
+ prune_captcha-1.12.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.7.1)
2
+ Generator: setuptools (80.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
captcha_prune/urls.py DELETED
@@ -1,10 +0,0 @@
1
- from django.urls import path
2
-
3
- from captcha_prune.views import create_captcha_view, verify_captcha_view
4
-
5
- app_name = "captcha"
6
-
7
- urlpatterns = [
8
- path("", create_captcha_view, name="create-captcha"),
9
- path("<str:uuid>/", verify_captcha_view, name="verify-captcha"),
10
- ]
captcha_prune/views.py DELETED
@@ -1,45 +0,0 @@
1
- from django.http import HttpRequest, HttpResponse, JsonResponse
2
- from django.shortcuts import get_object_or_404
3
- from django.views.decorators.csrf import csrf_exempt
4
- from django.views.decorators.http import require_GET, require_POST
5
-
6
- from captcha_prune.models import Captcha
7
- from captcha_prune.payloads import PuzzleAnswerPayload
8
- from commons.decorators import use_payload
9
-
10
-
11
- @require_POST
12
- @csrf_exempt
13
- def create_captcha_view(request: HttpRequest) -> HttpResponse:
14
- captcha = Captcha.objects.create()
15
- return JsonResponse(
16
- status=201,
17
- data={
18
- "uuid": str(captcha.uuid),
19
- "width": captcha.width,
20
- "height": captcha.height,
21
- "piece_width": captcha.piece_width,
22
- "piece_height": captcha.piece_height,
23
- "pos_x_solution": captcha.pos_x_solution,
24
- "pos_y_solution": captcha.pos_y_solution,
25
- "piece_pos_x": captcha.piece_pos_x,
26
- "piece_pos_y": captcha.piece_pos_y,
27
- },
28
- )
29
-
30
-
31
- @require_GET
32
- @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)
37
- pos_x_answer = payload.pos_x_answer
38
- pos_y_answer = payload.pos_y_answer
39
- if (
40
- abs(captcha.pos_x_solution - pos_x_answer) <= captcha.precision
41
- and abs(captcha.pos_y_solution - pos_y_answer) <= captcha.precision
42
- ):
43
- return HttpResponse(status=304)
44
- else:
45
- return HttpResponse(status=401)
commons/decorators.py DELETED
@@ -1,41 +0,0 @@
1
- import functools
2
- from typing import get_origin
3
-
4
- from django.http import HttpResponse
5
- from pydantic import ValidationError
6
-
7
-
8
- def use_payload(payload_class):
9
- def decorator(func):
10
- @functools.wraps(func)
11
- def wrapper(request, *args, **kwargs):
12
- data = {}
13
-
14
- if request.method == "POST":
15
- body = request.POST
16
- elif request.method == "GET":
17
- body = request.GET
18
- else:
19
- raise NotImplementedError
20
-
21
- for field_name, field in payload_class.model_fields.items():
22
- url_name = field.alias if field.alias else field_name
23
- is_list_field = get_origin(field.annotation) is list
24
- url_value = (
25
- body.getlist(url_name) if is_list_field else body.get(url_name)
26
- )
27
- if url_value is not None:
28
- data[url_name] = url_value
29
-
30
- try:
31
- payload = payload_class.model_validate(data)
32
- except ValidationError as e:
33
- return HttpResponse(
34
- e.json(), headers={"content_type": "application/json"}, status=400
35
- )
36
-
37
- return func(request, *args, payload=payload, **kwargs)
38
-
39
- return wrapper
40
-
41
- return decorator