Skip to content

Commit

Permalink
Testing against Django 5.0a and massive cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
mbi committed Sep 19, 2023
1 parent 9d94c6d commit e2f2b41
Show file tree
Hide file tree
Showing 18 changed files with 85 additions and 245 deletions.
6 changes: 6 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
Version History
===============


Version 0.6.0
-------------
* Only Django versions 4.2 or above are now supported
* Removed the old rendering methods that were deprecated in 2017.

Version 0.5.18
--------------
* Fix some typos in documentation (#210, thanks @stweil)
Expand Down
2 changes: 1 addition & 1 deletion captcha/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
VERSION = (0, 5, 18)
VERSION = (0, 6, 0)


def get_version():
Expand Down
19 changes: 0 additions & 19 deletions captcha/conf/settings.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import os
import warnings

from django.conf import settings

Expand Down Expand Up @@ -36,24 +35,6 @@
CAPTCHA_DICTIONARY_MIN_LENGTH = getattr(settings, "CAPTCHA_DICTIONARY_MIN_LENGTH", 0)
CAPTCHA_DICTIONARY_MAX_LENGTH = getattr(settings, "CAPTCHA_DICTIONARY_MAX_LENGTH", 99)
CAPTCHA_IMAGE_SIZE = getattr(settings, "CAPTCHA_IMAGE_SIZE", None)
CAPTCHA_IMAGE_TEMPLATE = getattr(
settings, "CAPTCHA_IMAGE_TEMPLATE", "captcha/image.html"
)
CAPTCHA_HIDDEN_FIELD_TEMPLATE = getattr(
settings, "CAPTCHA_HIDDEN_FIELD_TEMPLATE", "captcha/hidden_field.html"
)
CAPTCHA_TEXT_FIELD_TEMPLATE = getattr(
settings, "CAPTCHA_TEXT_FIELD_TEMPLATE", "captcha/text_field.html"
)

if getattr(settings, "CAPTCHA_FIELD_TEMPLATE", None):
msg = "CAPTCHA_FIELD_TEMPLATE setting is deprecated in favor of widget's template_name."
warnings.warn(msg, DeprecationWarning)
CAPTCHA_FIELD_TEMPLATE = getattr(settings, "CAPTCHA_FIELD_TEMPLATE", None)
if getattr(settings, "CAPTCHA_OUTPUT_FORMAT", None):
msg = "CAPTCHA_OUTPUT_FORMAT setting is deprecated in favor of widget's template_name."
warnings.warn(msg, DeprecationWarning)
CAPTCHA_OUTPUT_FORMAT = getattr(settings, "CAPTCHA_OUTPUT_FORMAT", None)

CAPTCHA_MATH_CHALLENGE_OPERATOR = getattr(
settings, "CAPTCHA_MATH_CHALLENGE_OPERATOR", "*"
Expand Down
64 changes: 4 additions & 60 deletions captcha/fields.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
import warnings

import django
from django.core.exceptions import ImproperlyConfigured
from django.forms import ValidationError
from django.forms.fields import CharField, MultiValueField
from django.forms.widgets import HiddenInput, MultiWidget, TextInput
from django.template.loader import render_to_string
from django.urls import NoReverseMatch, reverse
from django.utils import timezone
from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy

from captcha.conf import settings
Expand Down Expand Up @@ -97,34 +92,19 @@ def refresh_url(self):


class CaptchaTextInput(BaseCaptchaTextInput):

template_name = "captcha/widgets/captcha.html"

def __init__(
self,
attrs=None,
field_template=None,
id_prefix=None,
generator=None,
output_format=None,
# output_format=None,
):
self.id_prefix = id_prefix
self.generator = generator
if field_template is not None:
msg = "CaptchaTextInput's field_template argument is deprecated in favor of widget's template_name."
warnings.warn(msg, DeprecationWarning)
self.field_template = field_template or settings.CAPTCHA_FIELD_TEMPLATE
if output_format is not None:
msg = "CaptchaTextInput's output_format argument is deprecated in favor of widget's template_name."
warnings.warn(msg, DeprecationWarning)
self.output_format = output_format or settings.CAPTCHA_OUTPUT_FORMAT
# Fallback to custom rendering in Django < 1.11
if (
not hasattr(self, "_render")
and self.field_template is None
and self.output_format is None
):
self.field_template = "captcha/field.html"

self.output_format = None

if self.output_format:
for key in ("image", "hidden_field", "text_field"):
Expand Down Expand Up @@ -173,46 +153,11 @@ def format_output(self, rendered_widgets):
}
return ret

elif self.field_template:
context = {
"image": mark_safe(self.image_and_audio),
"hidden_field": mark_safe(self.hidden_field),
"text_field": mark_safe(self.text_field),
}
return render_to_string(self.field_template, context)

def _direct_render(self, name, attrs):
"""Render the widget the old way - using field_template or output_format."""
context = {
"image": self.image_url(),
"name": name,
"key": self._key,
"id": "%s_%s" % (self.id_prefix, attrs.get("id"))
if self.id_prefix
else attrs.get("id"),
"audio": self.audio_url(),
}
self.image_and_audio = render_to_string(
settings.CAPTCHA_IMAGE_TEMPLATE, context
)
self.hidden_field = render_to_string(
settings.CAPTCHA_HIDDEN_FIELD_TEMPLATE, context
)
self.text_field = render_to_string(
settings.CAPTCHA_TEXT_FIELD_TEMPLATE, context
)
return self.format_output(None)

def render(self, name, value, attrs=None, renderer=None):
self.fetch_captcha_store(name, value, attrs, self.generator)

if self.field_template or self.output_format:
return self._direct_render(name, attrs)

extra_kwargs = {}
if django.VERSION >= (1, 11):
# https://docs.djangoproject.com/en/1.11/ref/forms/widgets/#django.forms.Widget.render
extra_kwargs["renderer"] = renderer
extra_kwargs["renderer"] = renderer

return super(CaptchaTextInput, self).render(
name, self._value, attrs=attrs, **extra_kwargs
Expand All @@ -234,7 +179,6 @@ def __init__(self, *args, **kwargs):
kwargs["widget"] = kwargs.pop(
"widget",
CaptchaTextInput(
output_format=kwargs.pop("output_format", None),
id_prefix=kwargs.pop("id_prefix", None),
generator=kwargs.pop("generator", None),
),
Expand Down
1 change: 0 additions & 1 deletion captcha/templates/captcha/field.html

This file was deleted.

1 change: 0 additions & 1 deletion captcha/templates/captcha/hidden_field.html

This file was deleted.

4 changes: 0 additions & 4 deletions captcha/templates/captcha/image.html

This file was deleted.

1 change: 0 additions & 1 deletion captcha/templates/captcha/text_field.html

This file was deleted.

2 changes: 1 addition & 1 deletion captcha/templates/captcha/widgets/captcha.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@
<img src="{{ image }}" alt="captcha" class="captcha" />
{% if audio %}</a>{% endif %}
{% endspaceless %}
{% include "django/forms/widgets/multiwidget.html" %}
{% spaceless %}{% for widget in widget.subwidgets %}{% include widget.template_name %}{% endfor %}{% endspaceless %}
97 changes: 27 additions & 70 deletions captcha/tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import json
import os
import re
import warnings
from io import BytesIO

from PIL import Image
Expand All @@ -17,16 +16,13 @@
from django.utils.translation import gettext_lazy

from captcha.conf import settings
from captcha.fields import CaptchaField, CaptchaTextInput
from captcha.models import CaptchaStore


@override_settings(ROOT_URLCONF="captcha.tests.urls")
class CaptchaCase(TestCase):
def setUp(self):

self.stores = {}
self.__current_settings_output_format = settings.CAPTCHA_OUTPUT_FORMAT
self.__current_settings_dictionary = settings.CAPTCHA_WORDS_DICTIONARY
self.__current_settings_punctuation = settings.CAPTCHA_PUNCTUATION

Expand Down Expand Up @@ -57,7 +53,6 @@ def setUp(self):
self.default_store = self.stores["default_store"]

def tearDown(self):
settings.CAPTCHA_OUTPUT_FORMAT = self.__current_settings_output_format
settings.CAPTCHA_WORDS_DICTIONARY = self.__current_settings_dictionary
settings.CAPTCHA_PUNCTUATION = self.__current_settings_punctuation

Expand Down Expand Up @@ -273,47 +268,11 @@ def test_repeated_challenge_form_submit(self):
self.assertTrue(str(r2.content).find("Form validated") > 0)
settings.CAPTCHA_CHALLENGE_FUNCT = __current_challange_function

def test_output_format(self):
for urlname in ("captcha-test", "captcha-test-model-form"):
settings.CAPTCHA_OUTPUT_FORMAT = (
"%(image)s<p>Hello, captcha world</p>%(hidden_field)s%(text_field)s"
)
r = self.client.get(reverse(urlname))
self.assertEqual(r.status_code, 200)
self.assertTrue("<p>Hello, captcha world</p>" in str(r.content))

def test_invalid_output_format(self):
for urlname in ("captcha-test", "captcha-test-model-form"):
settings.CAPTCHA_OUTPUT_FORMAT = "%(image)s"
try:
with warnings.catch_warnings(record=True) as w:
self.client.get(reverse(urlname))
assert len(w) == 1
self.assertTrue("CAPTCHA_OUTPUT_FORMAT" in str(w[-1].message))
self.fail()

except ImproperlyConfigured as e:
self.assertTrue("CAPTCHA_OUTPUT_FORMAT" in str(e))

def test_per_form_format(self):
settings.CAPTCHA_OUTPUT_FORMAT = (
"%(image)s testCustomFormatString %(hidden_field)s %(text_field)s"
)
r = self.client.get(reverse("captcha-test"))
self.assertTrue("testCustomFormatString" in str(r.content))
r = self.client.get(reverse("test_per_form_format"))
self.assertTrue("testPerFieldCustomFormatString" in str(r.content))

def test_custom_generator(self):
r = self.client.get(reverse("test_custom_generator"))
hash_, response = self.__extract_hash_and_response(r)
self.assertEqual(response, "111111")

def test_issue31_proper_abel(self):
settings.CAPTCHA_OUTPUT_FORMAT = "%(image)s %(hidden_field)s %(text_field)s"
r = self.client.get(reverse("captcha-test"))
self.assertTrue('<label for="id_captcha_1"' in str(r.content))

def test_refresh_view(self):
r = self.client.get(
reverse("captcha-refresh"), HTTP_X_REQUESTED_WITH="XMLHttpRequest"
Expand All @@ -332,15 +291,6 @@ def test_content_length(self):
self.assertTrue(response["content-length"].isdigit())
self.assertTrue(int(response["content-length"]))

def test_issue12_proper_instantiation(self):
"""
This test covers a default django field and widget behavior
It not assert anything. If something is wrong it will raise a error!
"""
settings.CAPTCHA_OUTPUT_FORMAT = "%(image)s %(hidden_field)s %(text_field)s"
widget = CaptchaTextInput(attrs={"class": "required"})
CaptchaField(widget=widget)

def test_test_mode_issue15(self):
__current_test_mode_setting = settings.CAPTCHA_TEST_MODE
settings.CAPTCHA_TEST_MODE = False
Expand Down Expand Up @@ -424,9 +374,17 @@ def test_missing_value(self):
def test_autocomplete_off(self):
r = self.client.get(reverse("captcha-test"))
captcha_input = (
'<input type="text" name="captcha_1" autocomplete="off" spellcheck="false" autocorrect="off" '
'<input type="text" name="captcha_1" autocomplete="off" '
'spellcheck="false" autocorrect="off" '
'autocapitalize="off" id="id_captcha_1" required />'
)
if django.VERSION >= (5, 0):
captcha_input = (
'<input type="text" name="captcha_1" autocomplete="off" '
'spellcheck="false" autocorrect="off" '
'autocapitalize="off" id="id_captcha_1" '
'required aria-describedby="id_captcha_1_helptext" />'
)
self.assertContains(r, captcha_input, html=True)

def test_issue201_autocomplete_off_on_hiddeninput(self):
Expand All @@ -436,12 +394,24 @@ def test_issue201_autocomplete_off_on_hiddeninput(self):
key = r.context["form"]["captcha"].field.widget._key

# Assety that autocomplete=off is set on the hidden captcha field.
self.assertInHTML(
'<input type="hidden" name="captcha_0" value="{}" id="id_captcha_0" autocomplete="off" required />'.format(
key
),
str(r.content),
)
if django.VERSION >= (5, 0):
self.assertInHTML(
(
f'<input type="hidden" name="captcha_0" value="{key}" '
'id="id_captcha_0" autocomplete="off" '
'aria-describedby="id_captcha_1_helptext" required />'
),
str(r.content),
)

else:
self.assertInHTML(
(
f'<input type="hidden" name="captcha_0" value="{key}" '
'id="id_captcha_0" autocomplete="off" required />'
),
str(r.content),
)

def test_transparent_background(self):
__current_test_mode_setting = settings.CAPTCHA_BACKGROUND_COLOR
Expand Down Expand Up @@ -513,19 +483,6 @@ def test_multiple_fonts(self):

settings.CAPTCHA_FONT_PATH = __current_test_mode_setting

def test_template_overrides(self):
__current_test_mode_setting = settings.CAPTCHA_IMAGE_TEMPLATE
__current_field_template = settings.CAPTCHA_FIELD_TEMPLATE
settings.CAPTCHA_IMAGE_TEMPLATE = "captcha_test/image.html"
settings.CAPTCHA_FIELD_TEMPLATE = "captcha/field.html"

for urlname in ("captcha-test", "captcha-test-model-form"):
settings.CAPTCHA_CHALLENGE_FUNCT = "captcha.tests.trivial_challenge"
r = self.client.get(reverse(urlname))
self.assertTrue("captcha-template-test" in str(r.content))
settings.CAPTCHA_IMAGE_TEMPLATE = __current_test_mode_setting
settings.CAPTCHA_FIELD_TEMPLATE = __current_field_template

def test_math_challenge(self):
__current_test_mode_setting = settings.CAPTCHA_MATH_CHALLENGE_OPERATOR
settings.CAPTCHA_MATH_CHALLENGE_OPERATOR = "~"
Expand Down
Loading

0 comments on commit e2f2b41

Please sign in to comment.