added captcha to all forms
This commit is contained in:
parent
6c561e32e1
commit
841f0e9dbd
0
bin/activate
Normal file → Executable file
0
bin/activate
Normal file → Executable file
Binary file not shown.
Binary file not shown.
@ -15,7 +15,7 @@ Including another URLconf
|
|||||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||||
"""
|
"""
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import path
|
from django.urls import path, include
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from website.views import newspage, post, about, search, home, contact, datenschutz, impressum, anmeldung, tagungsplan, success, failure
|
from website.views import newspage, post, about, search, home, contact, datenschutz, impressum, anmeldung, tagungsplan, success, failure
|
||||||
@ -34,4 +34,5 @@ urlpatterns = [
|
|||||||
path('tagungsplan/', tagungsplan, name = 'tagungsplan'),
|
path('tagungsplan/', tagungsplan, name = 'tagungsplan'),
|
||||||
path('success/', success, name = 'success'),
|
path('success/', success, name = 'success'),
|
||||||
path('failure/', failure, name = 'failure'),
|
path('failure/', failure, name = 'failure'),
|
||||||
|
path('captcha/', include('captcha.urls')),
|
||||||
]
|
]
|
||||||
|
6
lib/python3.11/site-packages/captcha/__init__.py
Normal file
6
lib/python3.11/site-packages/captcha/__init__.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
VERSION = (0, 5, 20)
|
||||||
|
|
||||||
|
|
||||||
|
def get_version():
|
||||||
|
"Return the version as a human-format string."
|
||||||
|
return ".".join([str(i) for i in VERSION])
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
99
lib/python3.11/site-packages/captcha/conf/settings.py
Normal file
99
lib/python3.11/site-packages/captcha/conf/settings.py
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
import os
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
|
CAPTCHA_FONT_PATH = getattr(
|
||||||
|
settings,
|
||||||
|
"CAPTCHA_FONT_PATH",
|
||||||
|
os.path.normpath(os.path.join(os.path.dirname(__file__), "..", "fonts/Vera.ttf")),
|
||||||
|
)
|
||||||
|
CAPTCHA_FONT_SIZE = getattr(settings, "CAPTCHA_FONT_SIZE", 22)
|
||||||
|
CAPTCHA_LETTER_ROTATION = getattr(settings, "CAPTCHA_LETTER_ROTATION", (-35, 35))
|
||||||
|
CAPTCHA_BACKGROUND_COLOR = getattr(settings, "CAPTCHA_BACKGROUND_COLOR", "#ffffff")
|
||||||
|
CAPTCHA_FOREGROUND_COLOR = getattr(settings, "CAPTCHA_FOREGROUND_COLOR", "#001100")
|
||||||
|
CAPTCHA_CHALLENGE_FUNCT = getattr(
|
||||||
|
settings, "CAPTCHA_CHALLENGE_FUNCT", "captcha.helpers.random_char_challenge"
|
||||||
|
)
|
||||||
|
CAPTCHA_NOISE_FUNCTIONS = getattr(
|
||||||
|
settings,
|
||||||
|
"CAPTCHA_NOISE_FUNCTIONS",
|
||||||
|
("captcha.helpers.noise_arcs", "captcha.helpers.noise_dots"),
|
||||||
|
)
|
||||||
|
CAPTCHA_FILTER_FUNCTIONS = getattr(
|
||||||
|
settings, "CAPTCHA_FILTER_FUNCTIONS", ("captcha.helpers.post_smooth",)
|
||||||
|
)
|
||||||
|
CAPTCHA_WORDS_DICTIONARY = getattr(
|
||||||
|
settings, "CAPTCHA_WORDS_DICTIONARY", "/usr/share/dict/words"
|
||||||
|
)
|
||||||
|
CAPTCHA_PUNCTUATION = getattr(settings, "CAPTCHA_PUNCTUATION", """_"',.;:-""")
|
||||||
|
CAPTCHA_FLITE_PATH = getattr(settings, "CAPTCHA_FLITE_PATH", None)
|
||||||
|
CAPTCHA_SOX_PATH = getattr(settings, "CAPTCHA_SOX_PATH", None)
|
||||||
|
CAPTCHA_TIMEOUT = getattr(settings, "CAPTCHA_TIMEOUT", 5) # Minutes
|
||||||
|
CAPTCHA_LENGTH = int(getattr(settings, "CAPTCHA_LENGTH", 4)) # Chars
|
||||||
|
# CAPTCHA_IMAGE_BEFORE_FIELD = getattr(settings, 'CAPTCHA_IMAGE_BEFORE_FIELD', True)
|
||||||
|
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", "*"
|
||||||
|
)
|
||||||
|
CAPTCHA_GET_FROM_POOL = getattr(settings, "CAPTCHA_GET_FROM_POOL", False)
|
||||||
|
CAPTCHA_GET_FROM_POOL_TIMEOUT = getattr(settings, "CAPTCHA_GET_FROM_POOL_TIMEOUT", 5)
|
||||||
|
|
||||||
|
CAPTCHA_TEST_MODE = getattr(settings, "CAPTCHA_TEST_MODE", False)
|
||||||
|
|
||||||
|
CAPTCHA_2X_IMAGE = getattr(settings, "CAPTCHA_2X_IMAGE", True)
|
||||||
|
|
||||||
|
# Failsafe
|
||||||
|
if CAPTCHA_DICTIONARY_MIN_LENGTH > CAPTCHA_DICTIONARY_MAX_LENGTH:
|
||||||
|
CAPTCHA_DICTIONARY_MIN_LENGTH, CAPTCHA_DICTIONARY_MAX_LENGTH = (
|
||||||
|
CAPTCHA_DICTIONARY_MAX_LENGTH,
|
||||||
|
CAPTCHA_DICTIONARY_MIN_LENGTH,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _callable_from_string(string_or_callable):
|
||||||
|
if callable(string_or_callable):
|
||||||
|
return string_or_callable
|
||||||
|
else:
|
||||||
|
return getattr(
|
||||||
|
__import__(".".join(string_or_callable.split(".")[:-1]), {}, {}, [""]),
|
||||||
|
string_or_callable.split(".")[-1],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_challenge(generator=None):
|
||||||
|
return _callable_from_string(generator or CAPTCHA_CHALLENGE_FUNCT)
|
||||||
|
|
||||||
|
|
||||||
|
def noise_functions():
|
||||||
|
if CAPTCHA_NOISE_FUNCTIONS:
|
||||||
|
return map(_callable_from_string, CAPTCHA_NOISE_FUNCTIONS)
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def filter_functions():
|
||||||
|
if CAPTCHA_FILTER_FUNCTIONS:
|
||||||
|
return map(_callable_from_string, CAPTCHA_FILTER_FUNCTIONS)
|
||||||
|
return []
|
276
lib/python3.11/site-packages/captcha/fields.py
Normal file
276
lib/python3.11/site-packages/captcha/fields.py
Normal file
@ -0,0 +1,276 @@
|
|||||||
|
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
|
||||||
|
from captcha.models import CaptchaStore
|
||||||
|
|
||||||
|
|
||||||
|
class CaptchaHiddenInput(HiddenInput):
|
||||||
|
"""Hidden input for the captcha key."""
|
||||||
|
|
||||||
|
# Use *args and **kwargs because signature changed in Django 1.11
|
||||||
|
def build_attrs(self, *args, **kwargs):
|
||||||
|
"""Disable autocomplete to prevent problems on page reload."""
|
||||||
|
|
||||||
|
attrs = super().build_attrs(*args, **kwargs)
|
||||||
|
attrs["autocomplete"] = "off"
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
|
||||||
|
class CaptchaAnswerInput(TextInput):
|
||||||
|
"""Text input for captcha answer."""
|
||||||
|
|
||||||
|
# Use *args and **kwargs because signature changed in Django 1.11
|
||||||
|
def build_attrs(self, *args, **kwargs):
|
||||||
|
"""Disable automatic corrections and completions."""
|
||||||
|
attrs = super().build_attrs(*args, **kwargs)
|
||||||
|
attrs["autocapitalize"] = "off"
|
||||||
|
attrs["autocomplete"] = "off"
|
||||||
|
attrs["autocorrect"] = "off"
|
||||||
|
attrs["spellcheck"] = "false"
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
|
||||||
|
class BaseCaptchaTextInput(MultiWidget):
|
||||||
|
"""
|
||||||
|
Base class for Captcha widgets
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, attrs=None):
|
||||||
|
widgets = (CaptchaHiddenInput(attrs), CaptchaAnswerInput(attrs))
|
||||||
|
super(BaseCaptchaTextInput, self).__init__(widgets, attrs)
|
||||||
|
|
||||||
|
def decompress(self, value):
|
||||||
|
if value:
|
||||||
|
return value.split(",")
|
||||||
|
return [None, None]
|
||||||
|
|
||||||
|
def fetch_captcha_store(self, name, value, attrs=None, generator=None):
|
||||||
|
"""
|
||||||
|
Fetches a new CaptchaStore
|
||||||
|
This has to be called inside render
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
reverse("captcha-image", args=("dummy",))
|
||||||
|
except NoReverseMatch:
|
||||||
|
raise ImproperlyConfigured(
|
||||||
|
"Make sure you've included captcha.urls as explained in the INSTALLATION section on http://readthedocs.org/docs/django-simple-captcha/en/latest/usage.html#installation"
|
||||||
|
)
|
||||||
|
|
||||||
|
if settings.CAPTCHA_GET_FROM_POOL:
|
||||||
|
key = CaptchaStore.pick()
|
||||||
|
else:
|
||||||
|
key = CaptchaStore.generate_key(generator)
|
||||||
|
|
||||||
|
# these can be used by format_output and render
|
||||||
|
self._value = [key, ""]
|
||||||
|
self._key = key
|
||||||
|
self.id_ = self.build_attrs(attrs).get("id", None)
|
||||||
|
|
||||||
|
def id_for_label(self, id_):
|
||||||
|
if id_:
|
||||||
|
return id_ + "_1"
|
||||||
|
return id_
|
||||||
|
|
||||||
|
def image_url(self):
|
||||||
|
return reverse("captcha-image", kwargs={"key": self._key})
|
||||||
|
|
||||||
|
def audio_url(self):
|
||||||
|
return (
|
||||||
|
reverse("captcha-audio", kwargs={"key": self._key})
|
||||||
|
if settings.CAPTCHA_FLITE_PATH
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
|
||||||
|
def refresh_url(self):
|
||||||
|
return reverse("captcha-refresh")
|
||||||
|
|
||||||
|
|
||||||
|
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,
|
||||||
|
):
|
||||||
|
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"
|
||||||
|
|
||||||
|
if self.output_format:
|
||||||
|
for key in ("image", "hidden_field", "text_field"):
|
||||||
|
if "%%(%s)s" % key not in self.output_format:
|
||||||
|
raise ImproperlyConfigured(
|
||||||
|
"All of %s must be present in your CAPTCHA_OUTPUT_FORMAT setting. Could not find %s"
|
||||||
|
% (
|
||||||
|
", ".join(
|
||||||
|
[
|
||||||
|
"%%(%s)s" % k
|
||||||
|
for k in ("image", "hidden_field", "text_field")
|
||||||
|
]
|
||||||
|
),
|
||||||
|
"%%(%s)s" % key,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
super(CaptchaTextInput, self).__init__(attrs)
|
||||||
|
|
||||||
|
def build_attrs(self, *args, **kwargs):
|
||||||
|
ret = super(CaptchaTextInput, self).build_attrs(*args, **kwargs)
|
||||||
|
if self.id_prefix and "id" in ret:
|
||||||
|
ret["id"] = "%s_%s" % (self.id_prefix, ret["id"])
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def id_for_label(self, id_):
|
||||||
|
ret = super(CaptchaTextInput, self).id_for_label(id_)
|
||||||
|
if self.id_prefix and "id" in ret:
|
||||||
|
ret = "%s_%s" % (self.id_prefix, ret)
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def get_context(self, name, value, attrs):
|
||||||
|
"""Add captcha specific variables to context."""
|
||||||
|
context = super(CaptchaTextInput, self).get_context(name, value, attrs)
|
||||||
|
context["image"] = self.image_url()
|
||||||
|
context["audio"] = self.audio_url()
|
||||||
|
return context
|
||||||
|
|
||||||
|
def format_output(self, rendered_widgets):
|
||||||
|
# hidden_field, text_field = rendered_widgets
|
||||||
|
if self.output_format:
|
||||||
|
ret = self.output_format % {
|
||||||
|
"image": self.image_and_audio,
|
||||||
|
"hidden_field": self.hidden_field,
|
||||||
|
"text_field": self.text_field,
|
||||||
|
}
|
||||||
|
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
|
||||||
|
|
||||||
|
return super(CaptchaTextInput, self).render(
|
||||||
|
name, self._value, attrs=attrs, **extra_kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CaptchaField(MultiValueField):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
fields = (CharField(show_hidden_initial=True), CharField())
|
||||||
|
if "error_messages" not in kwargs or "invalid" not in kwargs.get(
|
||||||
|
"error_messages"
|
||||||
|
):
|
||||||
|
if "error_messages" not in kwargs:
|
||||||
|
kwargs["error_messages"] = {}
|
||||||
|
kwargs["error_messages"].update(
|
||||||
|
{"invalid": gettext_lazy("Invalid CAPTCHA")}
|
||||||
|
)
|
||||||
|
|
||||||
|
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),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
super(CaptchaField, self).__init__(fields, *args, **kwargs)
|
||||||
|
|
||||||
|
def compress(self, data_list):
|
||||||
|
if data_list:
|
||||||
|
return ",".join(data_list)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def clean(self, value):
|
||||||
|
super(CaptchaField, self).clean(value)
|
||||||
|
response, value[1] = (value[1] or "").strip().lower(), ""
|
||||||
|
if not settings.CAPTCHA_GET_FROM_POOL:
|
||||||
|
CaptchaStore.remove_expired()
|
||||||
|
if settings.CAPTCHA_TEST_MODE and response.lower() == "passed":
|
||||||
|
# automatically pass the test
|
||||||
|
try:
|
||||||
|
# try to delete the captcha based on its hash
|
||||||
|
CaptchaStore.objects.get(hashkey=value[0]).delete()
|
||||||
|
except CaptchaStore.DoesNotExist:
|
||||||
|
# ignore errors
|
||||||
|
pass
|
||||||
|
elif not self.required and not response:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
CaptchaStore.objects.get(
|
||||||
|
response=response, hashkey=value[0], expiration__gt=timezone.now()
|
||||||
|
).delete()
|
||||||
|
except CaptchaStore.DoesNotExist:
|
||||||
|
raise ValidationError(
|
||||||
|
getattr(self, "error_messages", {}).get(
|
||||||
|
"invalid", gettext_lazy("Invalid CAPTCHA")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return value
|
123
lib/python3.11/site-packages/captcha/fonts/COPYRIGHT.TXT
Normal file
123
lib/python3.11/site-packages/captcha/fonts/COPYRIGHT.TXT
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
Bitstream Vera Fonts Copyright
|
||||||
|
|
||||||
|
The fonts have a generous copyright, allowing derivative works (as
|
||||||
|
long as "Bitstream" or "Vera" are not in the names), and full
|
||||||
|
redistribution (so long as they are not *sold* by themselves). They
|
||||||
|
can be be bundled, redistributed and sold with any software.
|
||||||
|
|
||||||
|
The fonts are distributed under the following copyright:
|
||||||
|
|
||||||
|
Copyright
|
||||||
|
=========
|
||||||
|
|
||||||
|
Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream
|
||||||
|
Vera is a trademark of Bitstream, Inc.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of the fonts accompanying this license ("Fonts") and associated
|
||||||
|
documentation files (the "Font Software"), to reproduce and distribute
|
||||||
|
the Font Software, including without limitation the rights to use,
|
||||||
|
copy, merge, publish, distribute, and/or sell copies of the Font
|
||||||
|
Software, and to permit persons to whom the Font Software is furnished
|
||||||
|
to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright and trademark notices and this permission notice
|
||||||
|
shall be included in all copies of one or more of the Font Software
|
||||||
|
typefaces.
|
||||||
|
|
||||||
|
The Font Software may be modified, altered, or added to, and in
|
||||||
|
particular the designs of glyphs or characters in the Fonts may be
|
||||||
|
modified and additional glyphs or characters may be added to the
|
||||||
|
Fonts, only if the fonts are renamed to names not containing either
|
||||||
|
the words "Bitstream" or the word "Vera".
|
||||||
|
|
||||||
|
This License becomes null and void to the extent applicable to Fonts
|
||||||
|
or Font Software that has been modified and is distributed under the
|
||||||
|
"Bitstream Vera" names.
|
||||||
|
|
||||||
|
The Font Software may be sold as part of a larger software package but
|
||||||
|
no copy of one or more of the Font Software typefaces may be sold by
|
||||||
|
itself.
|
||||||
|
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||||
|
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL
|
||||||
|
BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||||
|
OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL,
|
||||||
|
OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||||
|
OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT
|
||||||
|
SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||||
|
|
||||||
|
Except as contained in this notice, the names of Gnome, the Gnome
|
||||||
|
Foundation, and Bitstream Inc., shall not be used in advertising or
|
||||||
|
otherwise to promote the sale, use or other dealings in this Font
|
||||||
|
Software without prior written authorization from the Gnome Foundation
|
||||||
|
or Bitstream Inc., respectively. For further information, contact:
|
||||||
|
fonts at gnome dot org.
|
||||||
|
|
||||||
|
Copyright FAQ
|
||||||
|
=============
|
||||||
|
|
||||||
|
1. I don't understand the resale restriction... What gives?
|
||||||
|
|
||||||
|
Bitstream is giving away these fonts, but wishes to ensure its
|
||||||
|
competitors can't just drop the fonts as is into a font sale system
|
||||||
|
and sell them as is. It seems fair that if Bitstream can't make money
|
||||||
|
from the Bitstream Vera fonts, their competitors should not be able to
|
||||||
|
do so either. You can sell the fonts as part of any software package,
|
||||||
|
however.
|
||||||
|
|
||||||
|
2. I want to package these fonts separately for distribution and
|
||||||
|
sale as part of a larger software package or system. Can I do so?
|
||||||
|
|
||||||
|
Yes. A RPM or Debian package is a "larger software package" to begin
|
||||||
|
with, and you aren't selling them independently by themselves.
|
||||||
|
See 1. above.
|
||||||
|
|
||||||
|
3. Are derivative works allowed?
|
||||||
|
Yes!
|
||||||
|
|
||||||
|
4. Can I change or add to the font(s)?
|
||||||
|
Yes, but you must change the name(s) of the font(s).
|
||||||
|
|
||||||
|
5. Under what terms are derivative works allowed?
|
||||||
|
|
||||||
|
You must change the name(s) of the fonts. This is to ensure the
|
||||||
|
quality of the fonts, both to protect Bitstream and Gnome. We want to
|
||||||
|
ensure that if an application has opened a font specifically of these
|
||||||
|
names, it gets what it expects (though of course, using fontconfig,
|
||||||
|
substitutions could still could have occurred during font
|
||||||
|
opening). You must include the Bitstream copyright. Additional
|
||||||
|
copyrights can be added, as per copyright law. Happy Font Hacking!
|
||||||
|
|
||||||
|
6. If I have improvements for Bitstream Vera, is it possible they might get
|
||||||
|
adopted in future versions?
|
||||||
|
|
||||||
|
Yes. The contract between the Gnome Foundation and Bitstream has
|
||||||
|
provisions for working with Bitstream to ensure quality additions to
|
||||||
|
the Bitstream Vera font family. Please contact us if you have such
|
||||||
|
additions. Note, that in general, we will want such additions for the
|
||||||
|
entire family, not just a single font, and that you'll have to keep
|
||||||
|
both Gnome and Jim Lyles, Vera's designer, happy! To make sense to add
|
||||||
|
glyphs to the font, they must be stylistically in keeping with Vera's
|
||||||
|
design. Vera cannot become a "ransom note" font. Jim Lyles will be
|
||||||
|
providing a document describing the design elements used in Vera, as a
|
||||||
|
guide and aid for people interested in contributing to Vera.
|
||||||
|
|
||||||
|
7. I want to sell a software package that uses these fonts: Can I do so?
|
||||||
|
|
||||||
|
Sure. Bundle the fonts with your software and sell your software
|
||||||
|
with the fonts. That is the intent of the copyright.
|
||||||
|
|
||||||
|
8. If applications have built the names "Bitstream Vera" into them,
|
||||||
|
can I override this somehow to use fonts of my choosing?
|
||||||
|
|
||||||
|
This depends on exact details of the software. Most open source
|
||||||
|
systems and software (e.g., Gnome, KDE, etc.) are now converting to
|
||||||
|
use fontconfig (see www.fontconfig.org) to handle font configuration,
|
||||||
|
selection and substitution; it has provisions for overriding font
|
||||||
|
names and substituting alternatives. An example is provided by the
|
||||||
|
supplied local.conf file, which chooses the family Bitstream Vera for
|
||||||
|
"sans", "serif" and "monospace". Other software (e.g., the XFree86
|
||||||
|
core server) has other mechanisms for font substitution.
|
11
lib/python3.11/site-packages/captcha/fonts/README.TXT
Normal file
11
lib/python3.11/site-packages/captcha/fonts/README.TXT
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
Contained herin is the Bitstream Vera font family.
|
||||||
|
|
||||||
|
The Copyright information is found in the COPYRIGHT.TXT file (along
|
||||||
|
with being incorporated into the fonts themselves).
|
||||||
|
|
||||||
|
The releases notes are found in the file "RELEASENOTES.TXT".
|
||||||
|
|
||||||
|
We hope you enjoy Vera!
|
||||||
|
|
||||||
|
Bitstream, Inc.
|
||||||
|
The Gnome Project
|
BIN
lib/python3.11/site-packages/captcha/fonts/Vera.ttf
Normal file
BIN
lib/python3.11/site-packages/captcha/fonts/Vera.ttf
Normal file
Binary file not shown.
105
lib/python3.11/site-packages/captcha/helpers.py
Normal file
105
lib/python3.11/site-packages/captcha/helpers.py
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
import random
|
||||||
|
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from captcha.conf import settings
|
||||||
|
|
||||||
|
|
||||||
|
def math_challenge():
|
||||||
|
operators = ("+", "*", "-")
|
||||||
|
operands = (random.randint(1, 10), random.randint(1, 10))
|
||||||
|
operator = random.choice(operators)
|
||||||
|
if operands[0] < operands[1] and "-" == operator:
|
||||||
|
operands = (operands[1], operands[0])
|
||||||
|
challenge = "%d%s%d" % (operands[0], operator, operands[1])
|
||||||
|
return (
|
||||||
|
"{}=".format(challenge.replace("*", settings.CAPTCHA_MATH_CHALLENGE_OPERATOR)),
|
||||||
|
str(eval(challenge)),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def random_char_challenge():
|
||||||
|
chars, ret = "abcdefghijklmnopqrstuvwxyz", ""
|
||||||
|
for i in range(settings.CAPTCHA_LENGTH):
|
||||||
|
ret += random.choice(chars)
|
||||||
|
return ret.upper(), ret
|
||||||
|
|
||||||
|
|
||||||
|
def unicode_challenge():
|
||||||
|
chars, ret = "äàáëéèïíîöóòüúù", ""
|
||||||
|
for i in range(settings.CAPTCHA_LENGTH):
|
||||||
|
ret += random.choice(chars)
|
||||||
|
return ret.upper(), ret
|
||||||
|
|
||||||
|
|
||||||
|
def word_challenge():
|
||||||
|
fd = open(settings.CAPTCHA_WORDS_DICTIONARY, "r")
|
||||||
|
lines = fd.readlines()
|
||||||
|
fd.close()
|
||||||
|
while True:
|
||||||
|
word = random.choice(lines).strip()
|
||||||
|
if (
|
||||||
|
len(word) >= settings.CAPTCHA_DICTIONARY_MIN_LENGTH
|
||||||
|
and len(word) <= settings.CAPTCHA_DICTIONARY_MAX_LENGTH
|
||||||
|
):
|
||||||
|
break
|
||||||
|
return word.upper(), word.lower()
|
||||||
|
|
||||||
|
|
||||||
|
def huge_words_and_punctuation_challenge():
|
||||||
|
"Yay, undocumneted. Mostly used to test Issue 39 - http://code.google.com/p/django-simple-captcha/issues/detail?id=39"
|
||||||
|
fd = open(settings.CAPTCHA_WORDS_DICTIONARY, "rb")
|
||||||
|
lines = fd.readlines()
|
||||||
|
fd.close()
|
||||||
|
word = ""
|
||||||
|
while True:
|
||||||
|
word1 = random.choice(lines).strip()
|
||||||
|
word2 = random.choice(lines).strip()
|
||||||
|
punct = random.choice(settings.CAPTCHA_PUNCTUATION)
|
||||||
|
word = "%s%s%s" % (word1, punct, word2)
|
||||||
|
if (
|
||||||
|
len(word) >= settings.CAPTCHA_DICTIONARY_MIN_LENGTH
|
||||||
|
and len(word) <= settings.CAPTCHA_DICTIONARY_MAX_LENGTH
|
||||||
|
):
|
||||||
|
break
|
||||||
|
return word.upper(), word.lower()
|
||||||
|
|
||||||
|
|
||||||
|
def noise_arcs(draw, image):
|
||||||
|
size = image.size
|
||||||
|
draw.arc([-20, -20, size[0], 20], 0, 295, fill=settings.CAPTCHA_FOREGROUND_COLOR)
|
||||||
|
draw.line(
|
||||||
|
[-20, 20, size[0] + 20, size[1] - 20], fill=settings.CAPTCHA_FOREGROUND_COLOR
|
||||||
|
)
|
||||||
|
draw.line([-20, 0, size[0] + 20, size[1]], fill=settings.CAPTCHA_FOREGROUND_COLOR)
|
||||||
|
return draw
|
||||||
|
|
||||||
|
|
||||||
|
def noise_dots(draw, image):
|
||||||
|
size = image.size
|
||||||
|
for p in range(int(size[0] * size[1] * 0.1)):
|
||||||
|
draw.point(
|
||||||
|
(random.randint(0, size[0]), random.randint(0, size[1])),
|
||||||
|
fill=settings.CAPTCHA_FOREGROUND_COLOR,
|
||||||
|
)
|
||||||
|
return draw
|
||||||
|
|
||||||
|
|
||||||
|
def noise_null(draw, image):
|
||||||
|
return draw
|
||||||
|
|
||||||
|
|
||||||
|
def post_smooth(image):
|
||||||
|
from PIL import ImageFilter
|
||||||
|
|
||||||
|
return image.filter(ImageFilter.SMOOTH)
|
||||||
|
|
||||||
|
|
||||||
|
def captcha_image_url(key):
|
||||||
|
"""Return url to image. Need for ajax refresh and, etc"""
|
||||||
|
return reverse("captcha-image", args=[key])
|
||||||
|
|
||||||
|
|
||||||
|
def captcha_audio_url(key):
|
||||||
|
"""Return url to image. Need for ajax refresh and, etc"""
|
||||||
|
return reverse("captcha-audio", args=[key])
|
@ -0,0 +1,2 @@
|
|||||||
|
{% if audio %}<a href="{{ audio }}">{% endif %}<img src="{{ image }}" alt="captcha" class="captcha" />{% if audio %}</a>{% endif %}
|
||||||
|
{% include "django/forms/widgets/multiwidget.html" %}
|
Binary file not shown.
@ -0,0 +1,31 @@
|
|||||||
|
# django-simple-captcha Bulgarian translation
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the django-simple-captcha package.
|
||||||
|
# Venelin Stoykov <vkstoykov@gmail.com>, 2014.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: django-simple-captcha 0.4.1\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2014-02-10 14:43+0200\n"
|
||||||
|
"PO-Revision-Date: 2014-02-10 15:00+0200\n"
|
||||||
|
"Last-Translator: Venelin Stoykov <vkstoykov@gmail.com>\n"
|
||||||
|
"Language-Team: bg <vkstoykov@gmail.com>\n"
|
||||||
|
"Language: bg\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
|
||||||
|
|
||||||
|
#: fields.py:91
|
||||||
|
msgid "Play CAPTCHA as audio file"
|
||||||
|
msgstr "Чуй текста като аудио файл"
|
||||||
|
|
||||||
|
#: fields.py:106 fields.py:135 tests/tests.py:99 tests/tests.py:239
|
||||||
|
#: tests/tests.py:246
|
||||||
|
msgid "Invalid CAPTCHA"
|
||||||
|
msgstr "Сгрешен текст"
|
||||||
|
|
||||||
|
#: tests/tests.py:125
|
||||||
|
msgid "This field is required."
|
||||||
|
msgstr "Това поле е задължително"
|
Binary file not shown.
@ -0,0 +1,30 @@
|
|||||||
|
# Czech translation of django-simple-captcha.
|
||||||
|
# Copyright (C) 2012 THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
# Beda Kosata <bedrich.kosata@nic.cz>, 2012.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: 0.3.5\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2012-10-09 07:05+0200\n"
|
||||||
|
"PO-Revision-Date: 2012-10-09 07:08+0200\n"
|
||||||
|
"Last-Translator: Beda Kosata <bedrich.kosata@nic.cz>\n"
|
||||||
|
"Language-Team: Czech <>\n"
|
||||||
|
"Language: cs\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
|
||||||
|
|
||||||
|
#: fields.py:50
|
||||||
|
msgid "Play CAPTCHA as audio file"
|
||||||
|
msgstr "Přehrát captchu jako audio soubor"
|
||||||
|
|
||||||
|
#: fields.py:67 fields.py:99 tests/__init__.py:62
|
||||||
|
msgid "Invalid CAPTCHA"
|
||||||
|
msgstr "Neplatná CAPTCHA"
|
||||||
|
|
||||||
|
#: tests/__init__.py:88
|
||||||
|
msgid "This field is required."
|
||||||
|
msgstr "Toto pole je povinné."
|
Binary file not shown.
@ -0,0 +1,32 @@
|
|||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: django-simple-captcha\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2013-07-16 12:06+0200\n"
|
||||||
|
"PO-Revision-Date: 2013-07-16 12:10+0100\n"
|
||||||
|
"Last-Translator: Patrick Lauber <patrick.lauber@divio.ch>\n"
|
||||||
|
"Language-Team: DE <digi@treepy.com>\n"
|
||||||
|
"Language: de\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
"X-Generator: Poedit 1.5.7\n"
|
||||||
|
|
||||||
|
#: fields.py:90
|
||||||
|
msgid "Play CAPTCHA as audio file"
|
||||||
|
msgstr "CAPTCHA als Audiodatei abspielen."
|
||||||
|
|
||||||
|
#: fields.py:105 fields.py:134 tests/tests.py:99 tests/tests.py:239
|
||||||
|
#: tests/tests.py:246
|
||||||
|
msgid "Invalid CAPTCHA"
|
||||||
|
msgstr "Ungültiges CAPTCHA"
|
||||||
|
|
||||||
|
#: tests/tests.py:125
|
||||||
|
msgid "This field is required."
|
||||||
|
msgstr "Dieses Feld wird benötigt."
|
Binary file not shown.
@ -0,0 +1,30 @@
|
|||||||
|
# django-simple-captcha French translation.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
# Patrick Samson <maxcom@laposte.net>, 2010.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: django-simple-captcha 0.2.0\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2012-07-25 11:44+0300\n"
|
||||||
|
"PO-Revision-Date: 2010-09-16 12:16+0200\n"
|
||||||
|
"Last-Translator: Marco Bonetti <mbonetti@gmail.com>\n"
|
||||||
|
"Language-Team: en <mbonetti@gmail.com>\n"
|
||||||
|
"Language: en\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=n>1;\n"
|
||||||
|
|
||||||
|
#: fields.py:49
|
||||||
|
msgid "Play CAPTCHA as audio file"
|
||||||
|
msgstr "Play CAPTCHA as audio file"
|
||||||
|
|
||||||
|
#: fields.py:66 fields.py:89 tests/__init__.py:62
|
||||||
|
msgid "Invalid CAPTCHA"
|
||||||
|
msgstr "Invalid CAPTCHA"
|
||||||
|
|
||||||
|
#: tests/__init__.py:88
|
||||||
|
msgid "This field is required."
|
||||||
|
msgstr "This field is required."
|
Binary file not shown.
@ -0,0 +1,32 @@
|
|||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: django-simple-captcha\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2013-07-16 12:06+0200\n"
|
||||||
|
"PO-Revision-Date: 2014-05-20 21:22+0100\n"
|
||||||
|
"Last-Translator: https://github.com/dragosdobrota\n"
|
||||||
|
"Language-Team: es\n"
|
||||||
|
"Language: es\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
"X-Generator: Poedit 1.6.5\n"
|
||||||
|
|
||||||
|
#: fields.py:90
|
||||||
|
msgid "Play CAPTCHA as audio file"
|
||||||
|
msgstr "Reproducir CAPTCHA de audio"
|
||||||
|
|
||||||
|
#: fields.py:105 fields.py:134 tests/tests.py:99 tests/tests.py:239
|
||||||
|
#: tests/tests.py:246
|
||||||
|
msgid "Invalid CAPTCHA"
|
||||||
|
msgstr "CAPTCHA no válido"
|
||||||
|
|
||||||
|
#: tests/tests.py:125
|
||||||
|
msgid "This field is required."
|
||||||
|
msgstr "Este campo es obligatorio."
|
Binary file not shown.
@ -0,0 +1,31 @@
|
|||||||
|
# django-simple-captcha French translation.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
# Patrick Samson <maxcom@laposte.net>, 2010.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: django-simple-captcha 0.2.0\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2012-07-25 11:44+0300\n"
|
||||||
|
"PO-Revision-Date: 2020-10-04 19:08+0330\n"
|
||||||
|
"Last-Translator: Mehdi Namaki <mavenium@gmail.com>\n"
|
||||||
|
"Language-Team: fa <mavenium@gmail.com>\n"
|
||||||
|
"Language: fa\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=n>1;\n"
|
||||||
|
"X-Generator: Poedit 2.3\n"
|
||||||
|
|
||||||
|
#: fields.py:49
|
||||||
|
msgid "Play CAPTCHA as audio file"
|
||||||
|
msgstr "پخش کد امنیتی به عنوان یک پرونده صوتی"
|
||||||
|
|
||||||
|
#: fields.py:66 fields.py:89 tests/__init__.py:62
|
||||||
|
msgid "Invalid CAPTCHA"
|
||||||
|
msgstr "کد امنیتی صحیح نیست"
|
||||||
|
|
||||||
|
#: tests/__init__.py:88
|
||||||
|
msgid "This field is required."
|
||||||
|
msgstr "این فیلد اجباری است."
|
Binary file not shown.
@ -0,0 +1,30 @@
|
|||||||
|
# django-simple-captcha French translation.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
# Patrick Samson <maxcom@laposte.net>, 2010.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: django-simple-captcha 0.2.0\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2012-07-25 11:44+0300\n"
|
||||||
|
"PO-Revision-Date: 2010-09-16 12:16+0200\n"
|
||||||
|
"Last-Translator: Patrick Samson <maxcom@laposte.net>\n"
|
||||||
|
"Language-Team: fr <maxcom@laposte.net>\n"
|
||||||
|
"Language: fr\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=n>1;\n"
|
||||||
|
|
||||||
|
#: fields.py:49
|
||||||
|
msgid "Play CAPTCHA as audio file"
|
||||||
|
msgstr "Écouter la version audio"
|
||||||
|
|
||||||
|
#: fields.py:66 fields.py:89 tests/__init__.py:62
|
||||||
|
msgid "Invalid CAPTCHA"
|
||||||
|
msgstr "CAPTCHA invalide"
|
||||||
|
|
||||||
|
#: tests/__init__.py:88
|
||||||
|
msgid "This field is required."
|
||||||
|
msgstr "Ce champ est obligatoire."
|
Binary file not shown.
@ -0,0 +1,30 @@
|
|||||||
|
# django-simple-captcha Italian translation.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
# Arjuna Del Toso <stupidate@gmail.net>, 2012
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: django-simple-captcha 0.3.6\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2012-11-14 02:53+0000\n"
|
||||||
|
"PO-Revision-Date: 2012-11-14 02:53+0000\n"
|
||||||
|
"Last-Translator: Arjuna Del Toso <stupidate@gmail.com>\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Language-Team: Arjuna Del Toso <stupidate@gmail.com>\n"
|
||||||
|
"Language: it\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=n>1;\n"
|
||||||
|
|
||||||
|
#: .\fields.py:56
|
||||||
|
msgid "Play CAPTCHA as audio file"
|
||||||
|
msgstr "Ascolta la parola di controllo"
|
||||||
|
|
||||||
|
#: .\fields.py:71 .\fields.py:96 .\tests\__init__.py:70
|
||||||
|
msgid "Invalid CAPTCHA"
|
||||||
|
msgstr "Parola di controllo sbagliata"
|
||||||
|
|
||||||
|
#: .\tests\__init__.py:97
|
||||||
|
msgid "This field is required."
|
||||||
|
msgstr "Questo campo è obbligatorio"
|
Binary file not shown.
@ -0,0 +1,31 @@
|
|||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
# Keisuke URAGO <bravo@resourcez.org>, 2014.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: django-simple-captcha\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2014-02-13 07:11+0900\n"
|
||||||
|
"PO-Revision-Date: 2014-02-13 07:11+0900\n"
|
||||||
|
"Last-Translator: Keisuke URAGO <bravo@resourcez.org>\n"
|
||||||
|
"Language-Team: Keisuke URAGO <bravo@resourcez.org>\n"
|
||||||
|
"Language: ja\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||||
|
|
||||||
|
#: fields.py:90
|
||||||
|
msgid "Play CAPTCHA as audio file"
|
||||||
|
msgstr "CAPTCHAをオーディオで読み上げる"
|
||||||
|
|
||||||
|
#: fields.py:105 fields.py:134 tests/tests.py:99 tests/tests.py:239
|
||||||
|
#: tests/tests.py:246
|
||||||
|
msgid "Invalid CAPTCHA"
|
||||||
|
msgstr "CAPTCHAの値が違っています"
|
||||||
|
|
||||||
|
#: tests/tests.py:125
|
||||||
|
msgid "This field is required."
|
||||||
|
msgstr "この項目は必須です"
|
Binary file not shown.
@ -0,0 +1,31 @@
|
|||||||
|
# django-simple-captcha Dutch translation.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
# Leon de Rijke <leon@cmsworks.nl>, 2013.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: django-simple-captcha 0.3.6\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2013-02-15 13:26+0100\n"
|
||||||
|
"PO-Revision-Date: 2013-02-15 13:26+0100\n"
|
||||||
|
"Last-Translator: Leon de Rijke <leon@cmsworks.nl>\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Language-Team: Leon de Rijke <leon@cmsworks.nl>\n"
|
||||||
|
"Language: nl\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
|
||||||
|
|
||||||
|
#: fields.py:50
|
||||||
|
msgid "Play CAPTCHA as audio file"
|
||||||
|
msgstr "Speel CAPTCHA als audiobestand af"
|
||||||
|
|
||||||
|
#: fields.py:67 fields.py:94 tests/__init__.py:64 tests/__init__.py:186
|
||||||
|
#: tests/__init__.py:193
|
||||||
|
msgid "Invalid CAPTCHA"
|
||||||
|
msgstr "CAPTCHA ongeldig, probeer het opnieuw"
|
||||||
|
|
||||||
|
#: tests/__init__.py:90
|
||||||
|
msgid "This field is required."
|
||||||
|
msgstr "Dit veld is verplicht."
|
Binary file not shown.
@ -0,0 +1,32 @@
|
|||||||
|
# Polish translation for django-simple-captcha.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the django-simple-captcha package.
|
||||||
|
# Sławomir Zborowski <slawomir.zborowski@hotmail.com>, 2013.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: django-simple-captcha 0.3.6\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2013-08-18 18:49+0200\n"
|
||||||
|
"PO-Revision-Date: 2013-08-18 18:52+0200\n"
|
||||||
|
"Last-Translator: Sławomir Zborowski <slawomir.zborowski@hotmail.com>\n"
|
||||||
|
"Language-Team: Polisch\n"
|
||||||
|
"Language: pl\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
|
||||||
|
"|| n%100>=20) ? 1 : 2)\n"
|
||||||
|
|
||||||
|
#: fields.py:90
|
||||||
|
msgid "Play CAPTCHA as audio file"
|
||||||
|
msgstr "Odtwórz CAPTCHĘ jako plik dźwiękowy"
|
||||||
|
|
||||||
|
#: fields.py:105 fields.py:134 tests/tests.py:99 tests/tests.py:239
|
||||||
|
#: tests/tests.py:246
|
||||||
|
msgid "Invalid CAPTCHA"
|
||||||
|
msgstr "Niepoprawnie wpisana CAPTCHA"
|
||||||
|
|
||||||
|
#: tests/tests.py:125
|
||||||
|
msgid "This field is required."
|
||||||
|
msgstr "To pole jest wymagane."
|
Binary file not shown.
@ -0,0 +1,31 @@
|
|||||||
|
# django-simple-captcha Portuguese (Brazilian) translation.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
# Alisson Patricio <eu@alisson.net>, 2013.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: django-simple-captcha 0.3.7\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2013-05-18 10:58-0300\n"
|
||||||
|
"PO-Revision-Date: 2013-05-18 13:12-0300\n"
|
||||||
|
"Last-Translator: Alisson Patricio <eu@alisson.net>\n"
|
||||||
|
"Language-Team: Alisson Patricio <eu@alisson.net>\n"
|
||||||
|
"Language: pt_br\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||||
|
|
||||||
|
#: fields.py:49
|
||||||
|
msgid "Play CAPTCHA as audio file"
|
||||||
|
msgstr "Ouça o arquivo de áudio"
|
||||||
|
|
||||||
|
#: fields.py:66 fields.py:93 tests/__init__.py:69
|
||||||
|
#: tests/__init__.py:198 tests/__init__.py:205
|
||||||
|
msgid "Invalid CAPTCHA"
|
||||||
|
msgstr "Resposta inválida"
|
||||||
|
|
||||||
|
#: tests/__init__.py:95
|
||||||
|
msgid "This field is required."
|
||||||
|
msgstr "Este campo é obrigatório."
|
Binary file not shown.
@ -0,0 +1,31 @@
|
|||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: django-simple-captcha 0.3.7\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2012-07-25 11:17+0300\n"
|
||||||
|
"PO-Revision-Date: 2012-07-25 11:17+0300\n"
|
||||||
|
"Last-Translator: \n"
|
||||||
|
"Language-Team: ru <LL@li.org>\n"
|
||||||
|
"Language: ru\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
|
||||||
|
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n"
|
||||||
|
|
||||||
|
#: fields.py:49
|
||||||
|
msgid "Play CAPTCHA as audio file"
|
||||||
|
msgstr "Воспроизвести CAPTCHA в виде аудио файла"
|
||||||
|
|
||||||
|
#: fields.py:66 fields.py:89 tests/__init__.py:62
|
||||||
|
msgid "Invalid CAPTCHA"
|
||||||
|
msgstr "Неверный ответ"
|
||||||
|
|
||||||
|
#: tests/__init__.py:88
|
||||||
|
msgid "This field is required."
|
||||||
|
msgstr "Это поле обязательно для заполнения."
|
Binary file not shown.
@ -0,0 +1,32 @@
|
|||||||
|
# Slovak translation of django-simple-captcha.
|
||||||
|
# Copyright (C) 2013 THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
# Pavol Otto <pavol.otto@nic.cz>, 2013.
|
||||||
|
#
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: django-simple-captcha 0.3.7\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2013-10-15 17:16+0200\n"
|
||||||
|
"PO-Revision-Date: 2013-10-15 17:16+0200\n"
|
||||||
|
"Last-Translator: Pavol Otto <pavol.otto@nic.cz>\n"
|
||||||
|
"Language-Team: SK\n"
|
||||||
|
"Language: sk\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
|
||||||
|
|
||||||
|
#: fields.py:90
|
||||||
|
msgid "Play CAPTCHA as audio file"
|
||||||
|
msgstr "Prehrať captchu ako audio súbor"
|
||||||
|
|
||||||
|
#: fields.py:105 fields.py:134 tests/tests.py:99 tests/tests.py:239
|
||||||
|
#: tests/tests.py:246
|
||||||
|
msgid "Invalid CAPTCHA"
|
||||||
|
msgstr "Neplatná CAPTCHA"
|
||||||
|
|
||||||
|
#: tests/tests.py:125
|
||||||
|
msgid "This field is required."
|
||||||
|
msgstr "Toto pole je povinné."
|
Binary file not shown.
@ -0,0 +1,31 @@
|
|||||||
|
# django-simple-captcha Swedish translation.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
# Stefan Norman <stefan.norman@bricco.se>, 2018.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: django-simple-captcha 0.2.0\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2012-07-25 11:44+0300\n"
|
||||||
|
"PO-Revision-Date: 2018-12-03 06:41+0100\n"
|
||||||
|
"Language-Team: en <mbonetti@gmail.com>\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
"X-Generator: Poedit 2.2\n"
|
||||||
|
"Last-Translator: Stefan Norman <stefan.norman@bricco.se>\n"
|
||||||
|
"Language: sv\n"
|
||||||
|
|
||||||
|
#: fields.py:49
|
||||||
|
msgid "Play CAPTCHA as audio file"
|
||||||
|
msgstr "Spela CAPTCHA som ljudfil"
|
||||||
|
|
||||||
|
#: fields.py:66 fields.py:89 tests/__init__.py:62
|
||||||
|
msgid "Invalid CAPTCHA"
|
||||||
|
msgstr "Ogiltig CAPTCHA"
|
||||||
|
|
||||||
|
#: tests/__init__.py:88
|
||||||
|
msgid "This field is required."
|
||||||
|
msgstr "Detta fält är obligatoriskt."
|
Binary file not shown.
@ -0,0 +1,31 @@
|
|||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: django-simple-captcha 0.3.6\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2013-01-19 16:33-0200\n"
|
||||||
|
"PO-Revision-Date: 2013-01-19 20:52+0200\n"
|
||||||
|
"Last-Translator: Gokmen Gorgen <gokmen@alageek.com>\n"
|
||||||
|
"Language-Team: TR Gokmen Gorgen <gokmen@alageek.com>\n"
|
||||||
|
"Language: tr\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=1; plural=0\n"
|
||||||
|
|
||||||
|
#: fields.py:50
|
||||||
|
msgid "Play CAPTCHA as audio file"
|
||||||
|
msgstr "Değeri ses dosyası olarak çal"
|
||||||
|
|
||||||
|
#: fields.py:67 fields.py:94 tests/__init__.py:64 tests/__init__.py:186
|
||||||
|
#: tests/__init__.py:193
|
||||||
|
msgid "Invalid CAPTCHA"
|
||||||
|
msgstr "Geçersiz değer"
|
||||||
|
|
||||||
|
#: tests/__init__.py:90
|
||||||
|
msgid "This field is required."
|
||||||
|
msgstr "Bu alan zorunludur."
|
Binary file not shown.
@ -0,0 +1,32 @@
|
|||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: django-simple-captcha 0.3.6\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2014-02-21 22:05+0200\n"
|
||||||
|
"PO-Revision-Date: 2014-02-21 22:05+0200\n"
|
||||||
|
"Last-Translator: @FuriousCoder\n"
|
||||||
|
"Language-Team: uk @FuriousCoder\n"
|
||||||
|
"Language: uk\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
|
||||||
|
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
||||||
|
|
||||||
|
#: fields.py:91
|
||||||
|
msgid "Play CAPTCHA as audio file"
|
||||||
|
msgstr "Відтворити CAPTCHA як аудіо файл."
|
||||||
|
|
||||||
|
#: fields.py:106 fields.py:135 tests/tests.py:99 tests/tests.py:239
|
||||||
|
#: tests/tests.py:246
|
||||||
|
msgid "Invalid CAPTCHA"
|
||||||
|
msgstr "Неправильна відповідь."
|
||||||
|
|
||||||
|
#: tests/tests.py:125
|
||||||
|
msgid "This field is required."
|
||||||
|
msgstr "Це поле є обов'язковим."
|
Binary file not shown.
@ -0,0 +1,31 @@
|
|||||||
|
# django-simple-captcha Chinese Simplified translation.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
# Ming Chen <c.ming@live.com>, 2013.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: django-simple-captcha 0.3.6\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2013-03-01 05:04+0800\n"
|
||||||
|
"PO-Revision-Date: 2013-03-01 05:04+0800\n"
|
||||||
|
"Last-Translator: Ming Chen <c.ming@live.com>\n"
|
||||||
|
"Language-Team: zh_cn Ming Chen <c.ming@live.com>\n"
|
||||||
|
"Language: zh_cn\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=1; plural=0\n"
|
||||||
|
|
||||||
|
#: fields.py:49
|
||||||
|
msgid "Play CAPTCHA as audio file"
|
||||||
|
msgstr "使用语音方式播放认证码"
|
||||||
|
|
||||||
|
#: fields.py:66 fields.py:93 tests/__init__.py:69 tests/__init__.py:198
|
||||||
|
#: tests/__init__.py:205
|
||||||
|
msgid "Invalid CAPTCHA"
|
||||||
|
msgstr "认证码错误"
|
||||||
|
|
||||||
|
#: tests/__init__.py:95
|
||||||
|
msgid "This field is required."
|
||||||
|
msgstr "这个字段是必须的"
|
Binary file not shown.
@ -0,0 +1,31 @@
|
|||||||
|
# django-simple-captcha Chinese Simplified translation.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
# Ming Chen <c.ming@live.com>, 2013.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: django-simple-captcha 0.3.6\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2013-03-01 05:04+0800\n"
|
||||||
|
"PO-Revision-Date: 2013-03-01 05:04+0800\n"
|
||||||
|
"Last-Translator: Ming Chen <c.ming@live.com>\n"
|
||||||
|
"Language-Team: zh_cn Ming Chen <c.ming@live.com>\n"
|
||||||
|
"Language: zh_cn\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=1; plural=0\n"
|
||||||
|
|
||||||
|
#: fields.py:49
|
||||||
|
msgid "Play CAPTCHA as audio file"
|
||||||
|
msgstr "使用语音方式播放认证码"
|
||||||
|
|
||||||
|
#: fields.py:66 fields.py:93 tests/__init__.py:69 tests/__init__.py:198
|
||||||
|
#: tests/__init__.py:205
|
||||||
|
msgid "Invalid CAPTCHA"
|
||||||
|
msgstr "认证码错误"
|
||||||
|
|
||||||
|
#: tests/__init__.py:95
|
||||||
|
msgid "This field is required."
|
||||||
|
msgstr "这个字段是必须的"
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,29 @@
|
|||||||
|
import sys
|
||||||
|
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = "Clean up expired captcha hashkeys."
|
||||||
|
|
||||||
|
def handle(self, **options):
|
||||||
|
from captcha.models import CaptchaStore
|
||||||
|
|
||||||
|
verbose = int(options.get("verbosity"))
|
||||||
|
expired_keys = CaptchaStore.objects.filter(
|
||||||
|
expiration__lte=timezone.now()
|
||||||
|
).count()
|
||||||
|
if verbose >= 1:
|
||||||
|
print("Currently %d expired hashkeys" % expired_keys)
|
||||||
|
try:
|
||||||
|
CaptchaStore.remove_expired()
|
||||||
|
except Exception:
|
||||||
|
if verbose >= 1:
|
||||||
|
print("Unable to delete expired hashkeys.")
|
||||||
|
sys.exit(1)
|
||||||
|
if verbose >= 1:
|
||||||
|
if expired_keys > 0:
|
||||||
|
print("%d expired hashkeys removed." % expired_keys)
|
||||||
|
else:
|
||||||
|
print("No keys to remove.")
|
@ -0,0 +1,34 @@
|
|||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from django.db import transaction
|
||||||
|
|
||||||
|
from captcha.models import CaptchaStore
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
|
||||||
|
help = "Create a pool of random captchas."
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument(
|
||||||
|
"--pool-size",
|
||||||
|
type=int,
|
||||||
|
default=1000,
|
||||||
|
help="Number of new captchas to create, default=1000",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--cleanup-expired",
|
||||||
|
action="store_true",
|
||||||
|
default=True,
|
||||||
|
help="Cleanup expired captchas after creating new ones",
|
||||||
|
)
|
||||||
|
|
||||||
|
@transaction.atomic()
|
||||||
|
def handle(self, **options):
|
||||||
|
verbose = int(options.get("verbosity"))
|
||||||
|
count = options.get("pool_size")
|
||||||
|
CaptchaStore.create_pool(count)
|
||||||
|
verbose and self.stdout.write("Created %d new captchas\n" % count)
|
||||||
|
options.get("cleanup_expired") and CaptchaStore.remove_expired()
|
||||||
|
options.get("cleanup_expired") and verbose and self.stdout.write(
|
||||||
|
"Expired captchas cleaned up\n"
|
||||||
|
)
|
@ -0,0 +1,31 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models, migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = []
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="CaptchaStore",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.AutoField(
|
||||||
|
verbose_name="ID",
|
||||||
|
serialize=False,
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("challenge", models.CharField(max_length=32)),
|
||||||
|
("response", models.CharField(max_length=32)),
|
||||||
|
("hashkey", models.CharField(unique=True, max_length=40)),
|
||||||
|
("expiration", models.DateTimeField()),
|
||||||
|
],
|
||||||
|
options={},
|
||||||
|
bases=(models.Model,),
|
||||||
|
)
|
||||||
|
]
|
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 3.2.12 on 2022-03-06 11:51
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("captcha", "0001_initial"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="captchastore",
|
||||||
|
name="id",
|
||||||
|
field=models.AutoField(primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
]
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
88
lib/python3.11/site-packages/captcha/models.py
Normal file
88
lib/python3.11/site-packages/captcha/models.py
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import datetime
|
||||||
|
import hashlib
|
||||||
|
import logging
|
||||||
|
import random
|
||||||
|
import time
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
from django.utils import timezone
|
||||||
|
from django.utils.encoding import smart_str
|
||||||
|
|
||||||
|
from captcha.conf import settings as captcha_settings
|
||||||
|
|
||||||
|
|
||||||
|
# Heavily based on session key generation in Django
|
||||||
|
# Use the system (hardware-based) random number generator if it exists.
|
||||||
|
if hasattr(random, "SystemRandom"):
|
||||||
|
randrange = random.SystemRandom().randrange
|
||||||
|
else:
|
||||||
|
randrange = random.randrange
|
||||||
|
MAX_RANDOM_KEY = 18446744073709551616 # 2 << 63
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class CaptchaStore(models.Model):
|
||||||
|
id = models.AutoField(primary_key=True)
|
||||||
|
challenge = models.CharField(blank=False, max_length=32)
|
||||||
|
response = models.CharField(blank=False, max_length=32)
|
||||||
|
hashkey = models.CharField(blank=False, max_length=40, unique=True)
|
||||||
|
expiration = models.DateTimeField(blank=False)
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
self.response = self.response.lower()
|
||||||
|
if not self.expiration:
|
||||||
|
self.expiration = timezone.now() + datetime.timedelta(
|
||||||
|
minutes=int(captcha_settings.CAPTCHA_TIMEOUT)
|
||||||
|
)
|
||||||
|
if not self.hashkey:
|
||||||
|
key_ = (
|
||||||
|
smart_str(randrange(0, MAX_RANDOM_KEY))
|
||||||
|
+ smart_str(time.time())
|
||||||
|
+ smart_str(self.challenge, errors="ignore")
|
||||||
|
+ smart_str(self.response, errors="ignore")
|
||||||
|
).encode("utf8")
|
||||||
|
self.hashkey = hashlib.sha1(key_).hexdigest()
|
||||||
|
del key_
|
||||||
|
super(CaptchaStore, self).save(*args, **kwargs)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.challenge
|
||||||
|
|
||||||
|
def remove_expired(cls):
|
||||||
|
cls.objects.filter(expiration__lte=timezone.now()).delete()
|
||||||
|
|
||||||
|
remove_expired = classmethod(remove_expired)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def generate_key(cls, generator=None):
|
||||||
|
challenge, response = captcha_settings.get_challenge(generator)()
|
||||||
|
store = cls.objects.create(challenge=challenge, response=response)
|
||||||
|
|
||||||
|
return store.hashkey
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def pick(cls):
|
||||||
|
if not captcha_settings.CAPTCHA_GET_FROM_POOL:
|
||||||
|
return cls.generate_key()
|
||||||
|
|
||||||
|
def fallback():
|
||||||
|
logger.error("Couldn't get a captcha from pool, generating")
|
||||||
|
return cls.generate_key()
|
||||||
|
|
||||||
|
# Pick up a random item from pool
|
||||||
|
minimum_expiration = timezone.now() + datetime.timedelta(
|
||||||
|
minutes=int(captcha_settings.CAPTCHA_GET_FROM_POOL_TIMEOUT)
|
||||||
|
)
|
||||||
|
store = (
|
||||||
|
cls.objects.filter(expiration__gt=minimum_expiration).order_by("?").first()
|
||||||
|
)
|
||||||
|
|
||||||
|
return (store and store.hashkey) or fallback()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create_pool(cls, count=1000):
|
||||||
|
assert count > 0
|
||||||
|
while count > 0:
|
||||||
|
cls.generate_key()
|
||||||
|
count -= 1
|
@ -0,0 +1 @@
|
|||||||
|
{{image}}{{hidden_field}}{{text_field}}
|
@ -0,0 +1 @@
|
|||||||
|
<input id="{{id}}_0" name="{{name}}_0" type="hidden" value="{{key}}" />
|
@ -0,0 +1,4 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
{% spaceless %}
|
||||||
|
{% if audio %}<a title="{% trans "Play CAPTCHA as audio file" %}" href="{{audio}}">{% endif %}<img src="{{image}}" alt="captcha" class="captcha" />{% if audio %}</a>{% endif %}
|
||||||
|
{% endspaceless %}
|
@ -0,0 +1 @@
|
|||||||
|
<input autocapitalize="off" autocomplete="off" autocorrect="off" spellcheck="false" id="{{id}}_1" name="{{name}}_1" type="text" />
|
@ -0,0 +1,9 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
{% spaceless %}
|
||||||
|
{% if audio %}
|
||||||
|
<a title="{% trans "Play CAPTCHA as audio file" %}" href="{{ audio }}">
|
||||||
|
{% endif %}
|
||||||
|
<img src="{{ image }}" alt="captcha" class="captcha" />
|
||||||
|
{% if audio %}</a>{% endif %}
|
||||||
|
{% endspaceless %}
|
||||||
|
{% include "django/forms/widgets/multiwidget.html" %}
|
1
lib/python3.11/site-packages/captcha/tests/__init__.py
Normal file
1
lib/python3.11/site-packages/captcha/tests/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from .tests import CaptchaCase, trivial_challenge # NOQA
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
594
lib/python3.11/site-packages/captcha/tests/tests.py
Normal file
594
lib/python3.11/site-packages/captcha/tests/tests.py
Normal file
@ -0,0 +1,594 @@
|
|||||||
|
import datetime
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import warnings
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
from testfixtures import LogCapture
|
||||||
|
|
||||||
|
import django
|
||||||
|
from django.core import management
|
||||||
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
from django.test import TestCase, override_settings
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.utils import timezone
|
||||||
|
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
|
||||||
|
|
||||||
|
tested_helpers = [
|
||||||
|
"captcha.helpers.math_challenge",
|
||||||
|
"captcha.helpers.random_char_challenge",
|
||||||
|
"captcha.helpers.unicode_challenge",
|
||||||
|
]
|
||||||
|
if os.path.exists("/usr/share/dict/words"):
|
||||||
|
settings.CAPTCHA_WORDS_DICTIONARY = "/usr/share/dict/words"
|
||||||
|
settings.CAPTCHA_PUNCTUATION = ";-,."
|
||||||
|
tested_helpers.append("captcha.helpers.word_challenge")
|
||||||
|
tested_helpers.append(
|
||||||
|
"captcha.helpers.huge_words_and_punctuation_challenge"
|
||||||
|
)
|
||||||
|
for helper in tested_helpers:
|
||||||
|
challenge, response = settings._callable_from_string(helper)()
|
||||||
|
(
|
||||||
|
self.stores[helper.rsplit(".", 1)[-1].replace("_challenge", "_store")],
|
||||||
|
_,
|
||||||
|
) = CaptchaStore.objects.get_or_create(
|
||||||
|
challenge=challenge, response=response
|
||||||
|
)
|
||||||
|
challenge, response = settings.get_challenge()()
|
||||||
|
self.stores["default_store"], _ = CaptchaStore.objects.get_or_create(
|
||||||
|
challenge=challenge, response=response
|
||||||
|
)
|
||||||
|
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
|
||||||
|
|
||||||
|
def _assertFormError(self, response, form_name, *args, **kwargs):
|
||||||
|
if django.VERSION >= (4, 1):
|
||||||
|
self.assertFormError(response.context.get(form_name), *args, **kwargs)
|
||||||
|
else:
|
||||||
|
self.assertFormError(response, form_name, *args, **kwargs)
|
||||||
|
|
||||||
|
def __extract_hash_and_response(self, r):
|
||||||
|
hash_ = re.findall(r'value="([0-9a-f]+)"', str(r.content))[0]
|
||||||
|
response = CaptchaStore.objects.get(hashkey=hash_).response
|
||||||
|
return hash_, response
|
||||||
|
|
||||||
|
def test_image(self):
|
||||||
|
for key in [store.hashkey for store in self.stores.values()]:
|
||||||
|
response = self.client.get(reverse("captcha-image", kwargs=dict(key=key)))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertTrue(response.has_header("content-type"))
|
||||||
|
self.assertEqual(response["content-type"], "image/png")
|
||||||
|
|
||||||
|
def test_audio(self):
|
||||||
|
if not settings.CAPTCHA_FLITE_PATH:
|
||||||
|
return
|
||||||
|
for key in (
|
||||||
|
self.stores.get("math_store").hashkey,
|
||||||
|
self.stores.get("math_store").hashkey,
|
||||||
|
self.default_store.hashkey,
|
||||||
|
):
|
||||||
|
response = self.client.get(reverse("captcha-audio", kwargs=dict(key=key)))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertTrue(response.ranged_file.size > 1024)
|
||||||
|
self.assertTrue(response.has_header("content-type"))
|
||||||
|
self.assertEqual(response["content-type"], "audio/wav")
|
||||||
|
|
||||||
|
def test_form_submit(self):
|
||||||
|
r = self.client.get(reverse("captcha-test"))
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
hash_, response = self.__extract_hash_and_response(r)
|
||||||
|
|
||||||
|
r = self.client.post(
|
||||||
|
reverse("captcha-test"),
|
||||||
|
dict(
|
||||||
|
captcha_0=hash_,
|
||||||
|
captcha_1=response,
|
||||||
|
subject="xxx",
|
||||||
|
sender="asasd@asdasd.com",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertTrue(str(r.content).find("Form validated") > 0)
|
||||||
|
|
||||||
|
r = self.client.post(
|
||||||
|
reverse("captcha-test"),
|
||||||
|
dict(
|
||||||
|
captcha_0=hash_,
|
||||||
|
captcha_1=response,
|
||||||
|
subject="xxx",
|
||||||
|
sender="asasd@asdasd.com",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertFalse(str(r.content).find("Form validated") > 0)
|
||||||
|
|
||||||
|
def test_modelform(self):
|
||||||
|
r = self.client.get(reverse("captcha-test-model-form"))
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
hash_, response = self.__extract_hash_and_response(r)
|
||||||
|
|
||||||
|
r = self.client.post(
|
||||||
|
reverse("captcha-test-model-form"),
|
||||||
|
dict(
|
||||||
|
captcha_0=hash_,
|
||||||
|
captcha_1=response,
|
||||||
|
subject="xxx",
|
||||||
|
sender="asasd@asdasd.com",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertTrue(str(r.content).find("Form validated") > 0)
|
||||||
|
|
||||||
|
r = self.client.post(
|
||||||
|
reverse("captcha-test-model-form"),
|
||||||
|
dict(
|
||||||
|
captcha_0=hash_,
|
||||||
|
captcha_1=response,
|
||||||
|
subject="xxx",
|
||||||
|
sender="asasd@asdasd.com",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertFalse(str(r.content).find("Form validated") > 0)
|
||||||
|
|
||||||
|
def test_wrong_submit(self):
|
||||||
|
for urlname in ("captcha-test", "captcha-test-model-form"):
|
||||||
|
r = self.client.get(reverse(urlname))
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
r = self.client.post(
|
||||||
|
reverse(urlname),
|
||||||
|
dict(
|
||||||
|
captcha_0="abc",
|
||||||
|
captcha_1="wrong response",
|
||||||
|
subject="xxx",
|
||||||
|
sender="asasd@asdasd.com",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
self._assertFormError(r, "form", "captcha", gettext_lazy("Invalid CAPTCHA"))
|
||||||
|
|
||||||
|
def test_deleted_expired(self):
|
||||||
|
self.default_store.expiration = timezone.now() - datetime.timedelta(minutes=5)
|
||||||
|
self.default_store.save()
|
||||||
|
hash_ = self.default_store.hashkey
|
||||||
|
r = self.client.post(
|
||||||
|
reverse("captcha-test"),
|
||||||
|
dict(
|
||||||
|
captcha_0=hash_,
|
||||||
|
captcha_1=self.default_store.response,
|
||||||
|
subject="xxx",
|
||||||
|
sender="asasd@asdasd.com",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertFalse("Form validated" in str(r.content))
|
||||||
|
|
||||||
|
# expired -> deleted
|
||||||
|
try:
|
||||||
|
CaptchaStore.objects.get(hashkey=hash_)
|
||||||
|
self.fail()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_custom_error_message(self):
|
||||||
|
r = self.client.get(reverse("captcha-test-custom-error-message"))
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
# Wrong answer
|
||||||
|
r = self.client.post(
|
||||||
|
reverse("captcha-test-custom-error-message"),
|
||||||
|
dict(captcha_0="abc", captcha_1="wrong response"),
|
||||||
|
)
|
||||||
|
self._assertFormError(r, "form", "captcha", "TEST CUSTOM ERROR MESSAGE")
|
||||||
|
# empty answer
|
||||||
|
r = self.client.post(
|
||||||
|
reverse("captcha-test-custom-error-message"),
|
||||||
|
dict(captcha_0="abc", captcha_1=""),
|
||||||
|
)
|
||||||
|
self._assertFormError(
|
||||||
|
r, "form", "captcha", gettext_lazy("This field is required.")
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_repeated_challenge(self):
|
||||||
|
CaptchaStore.objects.create(challenge="xxx", response="xxx")
|
||||||
|
try:
|
||||||
|
CaptchaStore.objects.create(challenge="xxx", response="xxx")
|
||||||
|
except Exception:
|
||||||
|
self.fail()
|
||||||
|
|
||||||
|
def test_repeated_challenge_form_submit(self):
|
||||||
|
__current_challange_function = settings.CAPTCHA_CHALLENGE_FUNCT
|
||||||
|
for urlname in ("captcha-test", "captcha-test-model-form"):
|
||||||
|
settings.CAPTCHA_CHALLENGE_FUNCT = "captcha.tests.trivial_challenge"
|
||||||
|
|
||||||
|
r1 = self.client.get(reverse(urlname))
|
||||||
|
r2 = self.client.get(reverse(urlname))
|
||||||
|
self.assertEqual(r1.status_code, 200)
|
||||||
|
self.assertEqual(r2.status_code, 200)
|
||||||
|
if re.findall(r'value="([0-9a-f]+)"', str(r1.content)):
|
||||||
|
hash_1 = re.findall(r'value="([0-9a-f]+)"', str(r1.content))[0]
|
||||||
|
else:
|
||||||
|
self.fail()
|
||||||
|
|
||||||
|
if re.findall(r'value="([0-9a-f]+)"', str(r2.content)):
|
||||||
|
hash_2 = re.findall(r'value="([0-9a-f]+)"', str(r2.content))[0]
|
||||||
|
else:
|
||||||
|
self.fail()
|
||||||
|
try:
|
||||||
|
store_1 = CaptchaStore.objects.get(hashkey=hash_1)
|
||||||
|
store_2 = CaptchaStore.objects.get(hashkey=hash_2)
|
||||||
|
except Exception:
|
||||||
|
self.fail()
|
||||||
|
|
||||||
|
self.assertTrue(store_1.pk != store_2.pk)
|
||||||
|
self.assertTrue(store_1.response == store_2.response)
|
||||||
|
self.assertTrue(hash_1 != hash_2)
|
||||||
|
|
||||||
|
r1 = self.client.post(
|
||||||
|
reverse(urlname),
|
||||||
|
dict(
|
||||||
|
captcha_0=hash_1,
|
||||||
|
captcha_1=store_1.response,
|
||||||
|
subject="xxx",
|
||||||
|
sender="asasd@asdasd.com",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
self.assertEqual(r1.status_code, 200)
|
||||||
|
self.assertTrue(str(r1.content).find("Form validated") > 0)
|
||||||
|
|
||||||
|
try:
|
||||||
|
store_2 = CaptchaStore.objects.get(hashkey=hash_2)
|
||||||
|
except Exception:
|
||||||
|
self.fail()
|
||||||
|
|
||||||
|
r2 = self.client.post(
|
||||||
|
reverse(urlname),
|
||||||
|
dict(
|
||||||
|
captcha_0=hash_2,
|
||||||
|
captcha_1=store_2.response,
|
||||||
|
subject="xxx",
|
||||||
|
sender="asasd@asdasd.com",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
self.assertEqual(r2.status_code, 200)
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
new_data = json.loads(str(r.content, encoding="ascii"))
|
||||||
|
self.assertTrue("image_url" in new_data)
|
||||||
|
self.assertTrue("audio_url" in new_data)
|
||||||
|
except Exception:
|
||||||
|
self.fail()
|
||||||
|
|
||||||
|
def test_content_length(self):
|
||||||
|
for key in [store.hashkey for store in self.stores.values()]:
|
||||||
|
response = self.client.get(reverse("captcha-image", kwargs=dict(key=key)))
|
||||||
|
self.assertTrue(response.has_header("content-length"))
|
||||||
|
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
|
||||||
|
r = self.client.get(reverse("captcha-test"))
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
r = self.client.post(
|
||||||
|
reverse("captcha-test"),
|
||||||
|
dict(
|
||||||
|
captcha_0="abc",
|
||||||
|
captcha_1="wrong response",
|
||||||
|
subject="xxx",
|
||||||
|
sender="asasd@asdasd.com",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
self._assertFormError(r, "form", "captcha", gettext_lazy("Invalid CAPTCHA"))
|
||||||
|
|
||||||
|
settings.CAPTCHA_TEST_MODE = True
|
||||||
|
# Test mode, only 'PASSED' is accepted
|
||||||
|
r = self.client.get(reverse("captcha-test"))
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
r = self.client.post(
|
||||||
|
reverse("captcha-test"),
|
||||||
|
dict(
|
||||||
|
captcha_0="abc",
|
||||||
|
captcha_1="wrong response",
|
||||||
|
subject="xxx",
|
||||||
|
sender="asasd@asdasd.com",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
self._assertFormError(r, "form", "captcha", gettext_lazy("Invalid CAPTCHA"))
|
||||||
|
|
||||||
|
r = self.client.get(reverse("captcha-test"))
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
r = self.client.post(
|
||||||
|
reverse("captcha-test"),
|
||||||
|
dict(
|
||||||
|
captcha_0="abc",
|
||||||
|
captcha_1="passed",
|
||||||
|
subject="xxx",
|
||||||
|
sender="asasd@asdasd.com",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
self.assertTrue(str(r.content).find("Form validated") > 0)
|
||||||
|
settings.CAPTCHA_TEST_MODE = __current_test_mode_setting
|
||||||
|
|
||||||
|
def test_get_version(self):
|
||||||
|
import captcha
|
||||||
|
|
||||||
|
captcha.get_version()
|
||||||
|
|
||||||
|
def test_missing_value(self):
|
||||||
|
r = self.client.get(reverse("captcha-test-non-required"))
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
hash_, response = self.__extract_hash_and_response(r)
|
||||||
|
|
||||||
|
# Empty response is okay when required is False
|
||||||
|
r = self.client.post(
|
||||||
|
reverse("captcha-test-non-required"),
|
||||||
|
dict(subject="xxx", sender="asasd@asdasd.com"),
|
||||||
|
)
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertTrue(str(r.content).find("Form validated") > 0)
|
||||||
|
|
||||||
|
# But a valid response is okay, too
|
||||||
|
r = self.client.get(reverse("captcha-test-non-required"))
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
hash_, response = self.__extract_hash_and_response(r)
|
||||||
|
|
||||||
|
r = self.client.post(
|
||||||
|
reverse("captcha-test-non-required"),
|
||||||
|
dict(
|
||||||
|
captcha_0=hash_,
|
||||||
|
captcha_1=response,
|
||||||
|
subject="xxx",
|
||||||
|
sender="asasd@asdasd.com",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertTrue(str(r.content).find("Form validated") > 0)
|
||||||
|
|
||||||
|
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" '
|
||||||
|
'autocapitalize="off" id="id_captcha_1" required />'
|
||||||
|
)
|
||||||
|
self.assertContains(r, captcha_input, html=True)
|
||||||
|
|
||||||
|
def test_issue201_autocomplete_off_on_hiddeninput(self):
|
||||||
|
r = self.client.get(reverse("captcha-test"))
|
||||||
|
|
||||||
|
# Inspect the response context to find out the captcha key.
|
||||||
|
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),
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_transparent_background(self):
|
||||||
|
__current_test_mode_setting = settings.CAPTCHA_BACKGROUND_COLOR
|
||||||
|
settings.CAPTCHA_BACKGROUND_COLOR = "transparent"
|
||||||
|
for key in [store.hashkey for store in self.stores.values()]:
|
||||||
|
response = self.client.get(reverse("captcha-image", kwargs=dict(key=key)))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertTrue(response.has_header("content-type"))
|
||||||
|
self.assertEqual(response["content-type"], "image/png")
|
||||||
|
|
||||||
|
settings.CAPTCHA_BACKGROUND_COLOR = __current_test_mode_setting
|
||||||
|
|
||||||
|
def test_expired_captcha_returns_410(self):
|
||||||
|
for key in [store.hashkey for store in self.stores.values()]:
|
||||||
|
response = self.client.get(reverse("captcha-image", kwargs=dict(key=key)))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
CaptchaStore.objects.filter(hashkey=key).delete()
|
||||||
|
response = self.client.get(reverse("captcha-image", kwargs=dict(key=key)))
|
||||||
|
self.assertEqual(response.status_code, 410)
|
||||||
|
|
||||||
|
def test_id_prefix(self):
|
||||||
|
r = self.client.get(reverse("captcha-test-id-prefix"))
|
||||||
|
self.assertTrue(
|
||||||
|
'<label for="form1_id_captcha1_1">Captcha1:</label>' in str(r.content)
|
||||||
|
)
|
||||||
|
self.assertTrue('id="form1_id_captcha1_1"' in str(r.content))
|
||||||
|
self.assertTrue(
|
||||||
|
'<label for="form2_id_captcha2_1">Captcha2:</label>' in str(r.content)
|
||||||
|
)
|
||||||
|
self.assertTrue('id="form2_id_captcha2_1"' in str(r.content))
|
||||||
|
|
||||||
|
def test_image_size(self):
|
||||||
|
__current_test_mode_setting = settings.CAPTCHA_IMAGE_SIZE
|
||||||
|
for key in [store.hashkey for store in self.stores.values()]:
|
||||||
|
settings.CAPTCHA_IMAGE_SIZE = (201, 97)
|
||||||
|
response = self.client.get(reverse("captcha-image", kwargs=dict(key=key)))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertTrue(response.has_header("content-type"))
|
||||||
|
self.assertEqual(response["content-type"], "image/png")
|
||||||
|
self.assertEqual(Image.open(BytesIO(response.content)).size, (201, 97))
|
||||||
|
|
||||||
|
settings.CAPTCHA_IMAGE_SIZE = __current_test_mode_setting
|
||||||
|
|
||||||
|
def test_multiple_fonts(self):
|
||||||
|
vera = os.path.join(os.path.dirname(__file__), "..", "fonts", "Vera.ttf")
|
||||||
|
__current_test_mode_setting = settings.CAPTCHA_FONT_PATH
|
||||||
|
settings.CAPTCHA_FONT_PATH = vera
|
||||||
|
|
||||||
|
for key in [store.hashkey for store in self.stores.values()]:
|
||||||
|
response = self.client.get(reverse("captcha-image", kwargs=dict(key=key)))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(response["content-type"], "image/png")
|
||||||
|
|
||||||
|
settings.CAPTCHA_FONT_PATH = [vera, vera, vera]
|
||||||
|
for key in [store.hashkey for store in self.stores.values()]:
|
||||||
|
response = self.client.get(reverse("captcha-image", kwargs=dict(key=key)))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(response["content-type"], "image/png")
|
||||||
|
|
||||||
|
settings.CAPTCHA_FONT_PATH = False
|
||||||
|
for key in [store.hashkey for store in self.stores.values()]:
|
||||||
|
try:
|
||||||
|
response = self.client.get(
|
||||||
|
reverse("captcha-image", kwargs=dict(key=key))
|
||||||
|
)
|
||||||
|
self.fail()
|
||||||
|
except ImproperlyConfigured:
|
||||||
|
pass
|
||||||
|
|
||||||
|
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 = "~"
|
||||||
|
helper = "captcha.helpers.math_challenge"
|
||||||
|
challenge, response = settings._callable_from_string(helper)()
|
||||||
|
|
||||||
|
while settings.CAPTCHA_MATH_CHALLENGE_OPERATOR not in challenge:
|
||||||
|
challenge, response = settings._callable_from_string(helper)()
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
response,
|
||||||
|
str(
|
||||||
|
eval(
|
||||||
|
challenge.replace(settings.CAPTCHA_MATH_CHALLENGE_OPERATOR, "*")[
|
||||||
|
:-1
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
settings.CAPTCHA_MATH_CHALLENGE_OPERATOR = __current_test_mode_setting
|
||||||
|
|
||||||
|
def test_get_from_pool(self):
|
||||||
|
__current_test_get_from_pool_setting = settings.CAPTCHA_GET_FROM_POOL
|
||||||
|
__current_test_get_from_pool_timeout_setting = (
|
||||||
|
settings.CAPTCHA_GET_FROM_POOL_TIMEOUT
|
||||||
|
)
|
||||||
|
__current_test_timeout_setting = settings.CAPTCHA_TIMEOUT
|
||||||
|
settings.CAPTCHA_GET_FROM_POOL = True
|
||||||
|
settings.CAPTCHA_GET_FROM_POOL_TIMEOUT = 5
|
||||||
|
settings.CAPTCHA_TIMEOUT = 90
|
||||||
|
CaptchaStore.objects.all().delete() # Delete objects created during SetUp
|
||||||
|
POOL_SIZE = 10
|
||||||
|
CaptchaStore.create_pool(count=POOL_SIZE)
|
||||||
|
self.assertEqual(CaptchaStore.objects.count(), POOL_SIZE)
|
||||||
|
pool = CaptchaStore.objects.values_list("hashkey", flat=True)
|
||||||
|
random_pick = CaptchaStore.pick()
|
||||||
|
self.assertIn(random_pick, pool)
|
||||||
|
# pick() should not create any extra captcha
|
||||||
|
self.assertEqual(CaptchaStore.objects.count(), POOL_SIZE)
|
||||||
|
settings.CAPTCHA_GET_FROM_POOL = __current_test_get_from_pool_setting
|
||||||
|
settings.CAPTCHA_GET_FROM_POOL_TIMEOUT = (
|
||||||
|
__current_test_get_from_pool_timeout_setting
|
||||||
|
)
|
||||||
|
settings.CAPTCHA_TIMEOUT = __current_test_timeout_setting
|
||||||
|
|
||||||
|
def test_captcha_create_pool(self):
|
||||||
|
CaptchaStore.objects.all().delete() # Delete objects created during SetUp
|
||||||
|
POOL_SIZE = 10
|
||||||
|
management.call_command("captcha_create_pool", pool_size=POOL_SIZE, verbosity=0)
|
||||||
|
self.assertEqual(CaptchaStore.objects.count(), POOL_SIZE)
|
||||||
|
|
||||||
|
def test_empty_pool_fallback(self):
|
||||||
|
__current_test_get_from_pool_setting = settings.CAPTCHA_GET_FROM_POOL
|
||||||
|
settings.CAPTCHA_GET_FROM_POOL = True
|
||||||
|
CaptchaStore.objects.all().delete() # Delete objects created during SetUp
|
||||||
|
with LogCapture() as log:
|
||||||
|
CaptchaStore.pick()
|
||||||
|
log.check(
|
||||||
|
("captcha.models", "ERROR", "Couldn't get a captcha from pool, generating")
|
||||||
|
)
|
||||||
|
self.assertEqual(CaptchaStore.objects.count(), 1)
|
||||||
|
settings.CAPTCHA_GET_FROM_POOL = __current_test_get_from_pool_setting
|
||||||
|
|
||||||
|
|
||||||
|
def trivial_challenge():
|
||||||
|
return "trivial", "trivial"
|
27
lib/python3.11/site-packages/captcha/tests/urls.py
Normal file
27
lib/python3.11/site-packages/captcha/tests/urls.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
from django.urls import include, re_path
|
||||||
|
|
||||||
|
from .views import (
|
||||||
|
test,
|
||||||
|
test_custom_error_message,
|
||||||
|
test_custom_generator,
|
||||||
|
test_id_prefix,
|
||||||
|
test_model_form,
|
||||||
|
test_non_required,
|
||||||
|
test_per_form_format,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
re_path(r"test/$", test, name="captcha-test"),
|
||||||
|
re_path(r"test-modelform/$", test_model_form, name="captcha-test-model-form"),
|
||||||
|
re_path(
|
||||||
|
r"test2/$", test_custom_error_message, name="captcha-test-custom-error-message"
|
||||||
|
),
|
||||||
|
re_path(r"test3/$", test_per_form_format, name="test_per_form_format"),
|
||||||
|
re_path(r"custom-generator/$", test_custom_generator, name="test_custom_generator"),
|
||||||
|
re_path(
|
||||||
|
r"test-non-required/$", test_non_required, name="captcha-test-non-required"
|
||||||
|
),
|
||||||
|
re_path(r"test-id-prefix/$", test_id_prefix, name="captcha-test-id-prefix"),
|
||||||
|
re_path(r"", include("captcha.urls")),
|
||||||
|
]
|
128
lib/python3.11/site-packages/captcha/tests/views.py
Normal file
128
lib/python3.11/site-packages/captcha/tests/views.py
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
from django import forms
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.http import HttpResponse
|
||||||
|
from django.template import engines
|
||||||
|
|
||||||
|
from captcha.fields import CaptchaField
|
||||||
|
|
||||||
|
|
||||||
|
TEST_TEMPLATE = r"""
|
||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||||
|
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
|
||||||
|
<title>captcha test</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{% if passed %}
|
||||||
|
<p style="color:green">Form validated</p>
|
||||||
|
{% endif %}
|
||||||
|
{% if form.errors %}
|
||||||
|
{{form.errors}}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<form action="{% url 'captcha-test' %}" method="post">
|
||||||
|
{{form.as_p}}
|
||||||
|
<p><input type="submit" value="Continue →"></p>
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def _get_template(template_string):
|
||||||
|
return engines["django"].from_string(template_string)
|
||||||
|
|
||||||
|
|
||||||
|
def _test(request, form_class):
|
||||||
|
passed = False
|
||||||
|
if request.POST:
|
||||||
|
form = form_class(request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
passed = True
|
||||||
|
else:
|
||||||
|
form = form_class()
|
||||||
|
|
||||||
|
t = _get_template(TEST_TEMPLATE)
|
||||||
|
|
||||||
|
return HttpResponse(
|
||||||
|
t.render(context=dict(passed=passed, form=form), request=request)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test(request):
|
||||||
|
class CaptchaTestForm(forms.Form):
|
||||||
|
subject = forms.CharField(max_length=100)
|
||||||
|
sender = forms.EmailField()
|
||||||
|
captcha = CaptchaField(help_text="asdasd")
|
||||||
|
|
||||||
|
return _test(request, CaptchaTestForm)
|
||||||
|
|
||||||
|
|
||||||
|
def test_model_form(request):
|
||||||
|
class CaptchaTestModelForm(forms.ModelForm):
|
||||||
|
subject = forms.CharField(max_length=100)
|
||||||
|
sender = forms.EmailField()
|
||||||
|
captcha = CaptchaField(help_text="asdasd")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = User
|
||||||
|
fields = ("subject", "sender", "captcha")
|
||||||
|
|
||||||
|
return _test(request, CaptchaTestModelForm)
|
||||||
|
|
||||||
|
|
||||||
|
def test_custom_generator(request):
|
||||||
|
class CaptchaTestModelForm(forms.ModelForm):
|
||||||
|
subject = forms.CharField(max_length=100)
|
||||||
|
sender = forms.EmailField()
|
||||||
|
captcha = CaptchaField(generator=lambda: ("111111", "111111"))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = User
|
||||||
|
fields = ("subject", "sender", "captcha")
|
||||||
|
|
||||||
|
return _test(request, CaptchaTestModelForm)
|
||||||
|
|
||||||
|
|
||||||
|
def test_custom_error_message(request):
|
||||||
|
class CaptchaTestErrorMessageForm(forms.Form):
|
||||||
|
captcha = CaptchaField(
|
||||||
|
help_text="asdasd", error_messages=dict(invalid="TEST CUSTOM ERROR MESSAGE")
|
||||||
|
)
|
||||||
|
|
||||||
|
return _test(request, CaptchaTestErrorMessageForm)
|
||||||
|
|
||||||
|
|
||||||
|
def test_per_form_format(request):
|
||||||
|
class CaptchaTestFormatForm(forms.Form):
|
||||||
|
captcha = CaptchaField(
|
||||||
|
help_text="asdasd",
|
||||||
|
error_messages=dict(invalid="TEST CUSTOM ERROR MESSAGE"),
|
||||||
|
output_format=(
|
||||||
|
"%(image)s testPerFieldCustomFormatString "
|
||||||
|
"%(hidden_field)s %(text_field)s"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
return _test(request, CaptchaTestFormatForm)
|
||||||
|
|
||||||
|
|
||||||
|
def test_non_required(request):
|
||||||
|
class CaptchaTestForm(forms.Form):
|
||||||
|
sender = forms.EmailField()
|
||||||
|
subject = forms.CharField(max_length=100)
|
||||||
|
captcha = CaptchaField(help_text="asdasd", required=False)
|
||||||
|
|
||||||
|
return _test(request, CaptchaTestForm)
|
||||||
|
|
||||||
|
|
||||||
|
def test_id_prefix(request):
|
||||||
|
class CaptchaTestForm(forms.Form):
|
||||||
|
sender = forms.EmailField()
|
||||||
|
subject = forms.CharField(max_length=100)
|
||||||
|
captcha1 = CaptchaField(id_prefix="form1")
|
||||||
|
captcha2 = CaptchaField(id_prefix="form2")
|
||||||
|
|
||||||
|
return _test(request, CaptchaTestForm)
|
21
lib/python3.11/site-packages/captcha/urls.py
Normal file
21
lib/python3.11/site-packages/captcha/urls.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
from django.urls import re_path
|
||||||
|
|
||||||
|
from captcha import views
|
||||||
|
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
re_path(
|
||||||
|
r"image/(?P<key>\w+)/$",
|
||||||
|
views.captcha_image,
|
||||||
|
name="captcha-image",
|
||||||
|
kwargs={"scale": 1},
|
||||||
|
),
|
||||||
|
re_path(
|
||||||
|
r"image/(?P<key>\w+)@2/$",
|
||||||
|
views.captcha_image,
|
||||||
|
name="captcha-image-2x",
|
||||||
|
kwargs={"scale": 2},
|
||||||
|
),
|
||||||
|
re_path(r"audio/(?P<key>\w+).wav$", views.captcha_audio, name="captcha-audio"),
|
||||||
|
re_path(r"refresh/$", views.captcha_refresh, name="captcha-refresh"),
|
||||||
|
]
|
225
lib/python3.11/site-packages/captcha/views.py
Normal file
225
lib/python3.11/site-packages/captcha/views.py
Normal file
@ -0,0 +1,225 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
|
from PIL import Image, ImageDraw, ImageFont
|
||||||
|
from ranged_response import RangedFileResponse
|
||||||
|
|
||||||
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
from django.http import Http404, HttpResponse
|
||||||
|
|
||||||
|
from captcha.conf import settings
|
||||||
|
from captcha.helpers import captcha_audio_url, captcha_image_url
|
||||||
|
from captcha.models import CaptchaStore
|
||||||
|
|
||||||
|
|
||||||
|
# Distance of the drawn text from the top of the captcha image
|
||||||
|
DISTANCE_FROM_TOP = 4
|
||||||
|
|
||||||
|
|
||||||
|
def getsize(font, text):
|
||||||
|
if hasattr(font, "getbbox"):
|
||||||
|
_top, _left, _right, _bottom = font.getbbox(text)
|
||||||
|
return _right - _left, _bottom - _top
|
||||||
|
elif hasattr(font, "getoffset"):
|
||||||
|
return tuple([x + y for x, y in zip(font.getsize(text), font.getoffset(text))])
|
||||||
|
else:
|
||||||
|
return font.getsize(text)
|
||||||
|
|
||||||
|
|
||||||
|
def makeimg(size):
|
||||||
|
if settings.CAPTCHA_BACKGROUND_COLOR == "transparent":
|
||||||
|
image = Image.new("RGBA", size)
|
||||||
|
else:
|
||||||
|
image = Image.new("RGB", size, settings.CAPTCHA_BACKGROUND_COLOR)
|
||||||
|
return image
|
||||||
|
|
||||||
|
|
||||||
|
def captcha_image(request, key, scale=1):
|
||||||
|
if scale == 2 and not settings.CAPTCHA_2X_IMAGE:
|
||||||
|
raise Http404
|
||||||
|
try:
|
||||||
|
store = CaptchaStore.objects.get(hashkey=key)
|
||||||
|
except CaptchaStore.DoesNotExist:
|
||||||
|
# HTTP 410 Gone status so that crawlers don't index these expired urls.
|
||||||
|
return HttpResponse(status=410)
|
||||||
|
|
||||||
|
random.seed(key) # Do not generate different images for the same key
|
||||||
|
|
||||||
|
text = store.challenge
|
||||||
|
|
||||||
|
if isinstance(settings.CAPTCHA_FONT_PATH, str):
|
||||||
|
fontpath = settings.CAPTCHA_FONT_PATH
|
||||||
|
elif isinstance(settings.CAPTCHA_FONT_PATH, (list, tuple)):
|
||||||
|
fontpath = random.choice(settings.CAPTCHA_FONT_PATH)
|
||||||
|
else:
|
||||||
|
raise ImproperlyConfigured(
|
||||||
|
"settings.CAPTCHA_FONT_PATH needs to be a path to a font or list of paths to fonts"
|
||||||
|
)
|
||||||
|
|
||||||
|
if fontpath.lower().strip().endswith("ttf"):
|
||||||
|
font = ImageFont.truetype(fontpath, settings.CAPTCHA_FONT_SIZE * scale)
|
||||||
|
else:
|
||||||
|
font = ImageFont.load(fontpath)
|
||||||
|
|
||||||
|
if settings.CAPTCHA_IMAGE_SIZE:
|
||||||
|
size = settings.CAPTCHA_IMAGE_SIZE
|
||||||
|
else:
|
||||||
|
size = getsize(font, text)
|
||||||
|
size = (size[0] * 2, int(size[1] * 1.4))
|
||||||
|
|
||||||
|
image = makeimg(size)
|
||||||
|
xpos = 2
|
||||||
|
|
||||||
|
charlist = []
|
||||||
|
for char in text:
|
||||||
|
if char in settings.CAPTCHA_PUNCTUATION and len(charlist) >= 1:
|
||||||
|
charlist[-1] += char
|
||||||
|
else:
|
||||||
|
charlist.append(char)
|
||||||
|
for char in charlist:
|
||||||
|
fgimage = Image.new("RGB", size, settings.CAPTCHA_FOREGROUND_COLOR)
|
||||||
|
charimage = Image.new("L", getsize(font, " %s " % char), "#000000")
|
||||||
|
chardraw = ImageDraw.Draw(charimage)
|
||||||
|
chardraw.text((0, 0), " %s " % char, font=font, fill="#ffffff")
|
||||||
|
if settings.CAPTCHA_LETTER_ROTATION:
|
||||||
|
charimage = charimage.rotate(
|
||||||
|
random.randrange(*settings.CAPTCHA_LETTER_ROTATION),
|
||||||
|
expand=0,
|
||||||
|
resample=Image.BICUBIC,
|
||||||
|
)
|
||||||
|
charimage = charimage.crop(charimage.getbbox())
|
||||||
|
maskimage = Image.new("L", size)
|
||||||
|
|
||||||
|
maskimage.paste(
|
||||||
|
charimage,
|
||||||
|
(
|
||||||
|
xpos,
|
||||||
|
DISTANCE_FROM_TOP,
|
||||||
|
xpos + charimage.size[0],
|
||||||
|
DISTANCE_FROM_TOP + charimage.size[1],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
size = maskimage.size
|
||||||
|
image = Image.composite(fgimage, image, maskimage)
|
||||||
|
xpos = xpos + 2 + charimage.size[0]
|
||||||
|
|
||||||
|
if settings.CAPTCHA_IMAGE_SIZE:
|
||||||
|
# centering captcha on the image
|
||||||
|
tmpimg = makeimg(size)
|
||||||
|
tmpimg.paste(
|
||||||
|
image,
|
||||||
|
(
|
||||||
|
int((size[0] - xpos) / 2),
|
||||||
|
int((size[1] - charimage.size[1]) / 2 - DISTANCE_FROM_TOP),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
image = tmpimg.crop((0, 0, size[0], size[1]))
|
||||||
|
else:
|
||||||
|
image = image.crop((0, 0, xpos + 1, size[1]))
|
||||||
|
draw = ImageDraw.Draw(image)
|
||||||
|
|
||||||
|
for f in settings.noise_functions():
|
||||||
|
draw = f(draw, image)
|
||||||
|
for f in settings.filter_functions():
|
||||||
|
image = f(image)
|
||||||
|
|
||||||
|
out = BytesIO()
|
||||||
|
image.save(out, "PNG")
|
||||||
|
out.seek(0)
|
||||||
|
|
||||||
|
response = HttpResponse(content_type="image/png")
|
||||||
|
response.write(out.read())
|
||||||
|
response["Content-length"] = out.tell()
|
||||||
|
|
||||||
|
# At line :50 above we fixed the random seed so that we always generate the
|
||||||
|
# same image, see: https://github.com/mbi/django-simple-captcha/pull/194
|
||||||
|
# This is a problem though, because knowledge of the seed will let an attacker
|
||||||
|
# predict the next random (globally). We therefore reset the random here.
|
||||||
|
# Reported in https://github.com/mbi/django-simple-captcha/pull/221
|
||||||
|
random.seed()
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
def captcha_audio(request, key):
|
||||||
|
if settings.CAPTCHA_FLITE_PATH:
|
||||||
|
try:
|
||||||
|
store = CaptchaStore.objects.get(hashkey=key)
|
||||||
|
except CaptchaStore.DoesNotExist:
|
||||||
|
# HTTP 410 Gone status so that crawlers don't index these expired urls.
|
||||||
|
return HttpResponse(status=410)
|
||||||
|
|
||||||
|
text = store.challenge
|
||||||
|
if "captcha.helpers.math_challenge" == settings.CAPTCHA_CHALLENGE_FUNCT:
|
||||||
|
text = text.replace("*", "times").replace("-", "minus").replace("+", "plus")
|
||||||
|
else:
|
||||||
|
text = ", ".join(list(text))
|
||||||
|
path = str(os.path.join(tempfile.gettempdir(), "%s.wav" % key))
|
||||||
|
subprocess.call([settings.CAPTCHA_FLITE_PATH, "-t", text, "-o", path])
|
||||||
|
|
||||||
|
# Add arbitrary noise if sox is installed
|
||||||
|
if settings.CAPTCHA_SOX_PATH:
|
||||||
|
arbnoisepath = str(
|
||||||
|
os.path.join(tempfile.gettempdir(), "%s_arbitrary.wav") % key
|
||||||
|
)
|
||||||
|
mergedpath = str(os.path.join(tempfile.gettempdir(), "%s_merged.wav") % key)
|
||||||
|
subprocess.call(
|
||||||
|
[
|
||||||
|
settings.CAPTCHA_SOX_PATH,
|
||||||
|
"-r",
|
||||||
|
"8000",
|
||||||
|
"-n",
|
||||||
|
arbnoisepath,
|
||||||
|
"synth",
|
||||||
|
"2",
|
||||||
|
"brownnoise",
|
||||||
|
"gain",
|
||||||
|
"-15",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
subprocess.call(
|
||||||
|
[
|
||||||
|
settings.CAPTCHA_SOX_PATH,
|
||||||
|
"-m",
|
||||||
|
arbnoisepath,
|
||||||
|
path,
|
||||||
|
"-t",
|
||||||
|
"wavpcm",
|
||||||
|
"-b",
|
||||||
|
"16",
|
||||||
|
mergedpath,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
os.remove(arbnoisepath)
|
||||||
|
os.remove(path)
|
||||||
|
os.rename(mergedpath, path)
|
||||||
|
|
||||||
|
if os.path.isfile(path):
|
||||||
|
response = RangedFileResponse(
|
||||||
|
request, open(path, "rb"), content_type="audio/wav"
|
||||||
|
)
|
||||||
|
response["Content-Disposition"] = 'attachment; filename="{}.wav"'.format(
|
||||||
|
key
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
raise Http404
|
||||||
|
|
||||||
|
|
||||||
|
def captcha_refresh(request):
|
||||||
|
"""Return json with new captcha for ajax refresh request"""
|
||||||
|
if not request.headers.get("x-requested-with") == "XMLHttpRequest":
|
||||||
|
raise Http404
|
||||||
|
|
||||||
|
new_key = CaptchaStore.pick()
|
||||||
|
to_json_response = {
|
||||||
|
"key": new_key,
|
||||||
|
"image_url": captcha_image_url(new_key),
|
||||||
|
"audio_url": captcha_audio_url(new_key)
|
||||||
|
if settings.CAPTCHA_FLITE_PATH
|
||||||
|
else None,
|
||||||
|
}
|
||||||
|
return HttpResponse(json.dumps(to_json_response), content_type="application/json")
|
@ -0,0 +1,10 @@
|
|||||||
|
Metadata-Version: 2.1
|
||||||
|
Name: django-ranged-response
|
||||||
|
Version: 0.2.0
|
||||||
|
Summary: Modified Django FileResponse that adds Content-Range headers.
|
||||||
|
Home-page: https://github.com/wearespindle/django-ranged-fileresponse
|
||||||
|
Author: Spindle
|
||||||
|
Author-email: jeroen@wearespindle.com
|
||||||
|
License: MIT
|
||||||
|
Requires-Dist: django
|
||||||
|
|
@ -0,0 +1,7 @@
|
|||||||
|
django_ranged_response-0.2.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||||
|
django_ranged_response-0.2.0.dist-info/METADATA,sha256=zD0L0iFcE_b2_nSKGUFYT3eqSzdKZ6eRt1GNUuAa4ME,297
|
||||||
|
django_ranged_response-0.2.0.dist-info/RECORD,,
|
||||||
|
django_ranged_response-0.2.0.dist-info/WHEEL,sha256=Xo9-1PvkuimrydujYJAjF7pCkriuXBpUPEjma1nZyJ0,92
|
||||||
|
django_ranged_response-0.2.0.dist-info/top_level.txt,sha256=1Vr01kwR92rMMkPOVv4QTsqa-gt5dxCPiRcPM0Qahak,16
|
||||||
|
ranged_response/__init__.py,sha256=gURFm41igrS4xwZHO83i57ta6uXAxMPoJO9KgZLDpOg,5057
|
||||||
|
ranged_response/__pycache__/__init__.cpython-311.pyc,,
|
@ -1,5 +1,5 @@
|
|||||||
Wheel-Version: 1.0
|
Wheel-Version: 1.0
|
||||||
Generator: bdist_wheel (0.40.0)
|
Generator: bdist_wheel (0.41.3)
|
||||||
Root-Is-Purelib: true
|
Root-Is-Purelib: true
|
||||||
Tag: py3-none-any
|
Tag: py3-none-any
|
||||||
|
|
@ -0,0 +1 @@
|
|||||||
|
ranged_response
|
@ -0,0 +1,20 @@
|
|||||||
|
Copyright (c) 2008 - 2014 Marco Bonetti
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
@ -0,0 +1,65 @@
|
|||||||
|
Metadata-Version: 2.1
|
||||||
|
Name: django-simple-captcha
|
||||||
|
Version: 0.5.20
|
||||||
|
Summary: A very simple, yet powerful, Django captcha application
|
||||||
|
Home-page: https://github.com/mbi/django-simple-captcha
|
||||||
|
Author: Marco Bonetti
|
||||||
|
Author-email: mbonetti@gmail.com
|
||||||
|
License: MIT
|
||||||
|
Classifier: Development Status :: 4 - Beta
|
||||||
|
Classifier: Environment :: Web Environment
|
||||||
|
Classifier: Intended Audience :: Developers
|
||||||
|
Classifier: License :: OSI Approved :: MIT License
|
||||||
|
Classifier: Operating System :: OS Independent
|
||||||
|
Classifier: Programming Language :: Python
|
||||||
|
Classifier: Programming Language :: Python :: 3.9
|
||||||
|
Classifier: Programming Language :: Python :: 3.10
|
||||||
|
Classifier: Programming Language :: Python :: 3.11
|
||||||
|
Classifier: Topic :: Security
|
||||||
|
Classifier: Topic :: Internet :: WWW/HTTP
|
||||||
|
Classifier: Framework :: Django
|
||||||
|
License-File: LICENSE
|
||||||
|
Requires-Dist: Django >=3.2
|
||||||
|
Requires-Dist: Pillow >=6.2.0
|
||||||
|
Requires-Dist: django-ranged-response ==0.2.0
|
||||||
|
Provides-Extra: test
|
||||||
|
Requires-Dist: testfixtures ; extra == 'test'
|
||||||
|
|
||||||
|
*********************
|
||||||
|
Django Simple Captcha
|
||||||
|
*********************
|
||||||
|
|
||||||
|
.. image:: https://github.com/mbi/django-simple-captcha/actions/workflows/test.yml/badge.svg
|
||||||
|
:target: https://github.com/mbi/django-simple-captcha/actions/workflows/test.yml
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/pypi/v/django-simple-captcha
|
||||||
|
:target: https://pypi.org/project/django-simple-captcha/
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/pypi/l/django-simple-captcha
|
||||||
|
:target: https://github.com/mbi/django-simple-captcha/blob/master/LICENSE
|
||||||
|
|
||||||
|
|
||||||
|
Django Simple Captcha is an extremely simple, yet highly customizable Django application to add captcha images to any Django form.
|
||||||
|
|
||||||
|
.. image:: http://django-simple-captcha.readthedocs.io/en/latest/_images/captcha3.png
|
||||||
|
|
||||||
|
Features
|
||||||
|
++++++++
|
||||||
|
|
||||||
|
* Very simple to setup and deploy, yet very configurable
|
||||||
|
* Can use custom challenges (e.g. random chars, simple math, dictionary word, ...)
|
||||||
|
* Custom generators, noise and filter functions alter the look of the generated image
|
||||||
|
* Supports text-to-speech audio output of the challenge text, for improved accessibility
|
||||||
|
* Ajax refresh
|
||||||
|
|
||||||
|
Requirements
|
||||||
|
++++++++++++
|
||||||
|
|
||||||
|
* Django 3.2+, Python3.8+
|
||||||
|
* A recent version of the Pillow compiled with FreeType support
|
||||||
|
* Flite is required for text-to-speech (audio) output, but not mandatory
|
||||||
|
|
||||||
|
Documentation
|
||||||
|
+++++++++++++
|
||||||
|
|
||||||
|
Read the `documentation online <http://django-simple-captcha.readthedocs.org/en/latest/>`_.
|
@ -0,0 +1,92 @@
|
|||||||
|
captcha/__init__.py,sha256=Cz2BKUx-KbJ2yLXHZ9JdfVIsLBP2ugtIoCK65Ywx79Q,140
|
||||||
|
captcha/__pycache__/__init__.cpython-311.pyc,,
|
||||||
|
captcha/__pycache__/fields.cpython-311.pyc,,
|
||||||
|
captcha/__pycache__/helpers.cpython-311.pyc,,
|
||||||
|
captcha/__pycache__/models.cpython-311.pyc,,
|
||||||
|
captcha/__pycache__/urls.cpython-311.pyc,,
|
||||||
|
captcha/__pycache__/views.cpython-311.pyc,,
|
||||||
|
captcha/conf/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||||
|
captcha/conf/__pycache__/__init__.cpython-311.pyc,,
|
||||||
|
captcha/conf/__pycache__/settings.cpython-311.pyc,,
|
||||||
|
captcha/conf/settings.py,sha256=AH5rdbUoZWqRbZO8KpI2EChKa6SKepS-KLRFPPhP9w4,3845
|
||||||
|
captcha/fields.py,sha256=DEaPQ8CVjuhFowDVNXAoOQI7L0WN2lfh_X9NctVSOGQ,9889
|
||||||
|
captcha/fonts/COPYRIGHT.TXT,sha256=ICRpD7nb_bF8U2uD1MYwp2Dt1ROoeK5hihPPWG-iwWw,5950
|
||||||
|
captcha/fonts/README.TXT,sha256=J3YHBi0jkpUjqB4vMrfoQ08MAUhr-gbngH8q1SVYXc8,321
|
||||||
|
captcha/fonts/Vera.ttf,sha256=xMRWkLNFQ1ssulLsq-J18F5Js4mzn-aK0Dr7tVEojT0,65932
|
||||||
|
captcha/helpers.py,sha256=LWDyxeYjPGiXQCZWIlxChJR_0t8wrOuyDbon5FQhAV8,3069
|
||||||
|
captcha/jinja2/captcha/widgets/captcha.html,sha256=UNvyLbRvQCkGp_AbOKrrfy1d6iXF1fozqxiMw8fRm9I,186
|
||||||
|
captcha/locale/bg/LC_MESSAGES/django.mo,sha256=UAkafercICutRXyYrBeExNF0-KrzLJ-tw2OK5RNkMJo,653
|
||||||
|
captcha/locale/bg/LC_MESSAGES/django.po,sha256=5o14ZmKzCmfdhCpiRyb-fVBkwldIc_NBNaDLVCA2AuI,1060
|
||||||
|
captcha/locale/cs/LC_MESSAGES/django.mo,sha256=AHs5edgI79WL2VACnNTmcnSaN7GC2WCEbJBl7zp5vgo,597
|
||||||
|
captcha/locale/cs/LC_MESSAGES/django.po,sha256=hJcBhAQZdFnZpqZwhpuDgaEzrEl-zY2ANY-kBX0tSTY,950
|
||||||
|
captcha/locale/de/LC_MESSAGES/django.mo,sha256=5hZSrteOGmsf6UX0F-sPiL5ZLRjYmc2mWHxJ601_TJ8,634
|
||||||
|
captcha/locale/de/LC_MESSAGES/django.po,sha256=HnWs3cwc2AFekknV2vgw-bJVWNbnKNmgS7cvjIMZYO0,1002
|
||||||
|
captcha/locale/en/LC_MESSAGES/django.mo,sha256=oOYm0ZDZinVXRs-9KaqtbNz3opWjc6-psC9rgOwc-x4,591
|
||||||
|
captcha/locale/en/LC_MESSAGES/django.po,sha256=FGoY9PDI0B1TsSBy6ytnA_oJoPpRjZg8L8P-W4A0fJ4,942
|
||||||
|
captcha/locale/es/LC_MESSAGES/django.mo,sha256=qWdpPZe45meLuxcoKbDslGn0-UFoZt8hrYfIGZaJgDU,600
|
||||||
|
captcha/locale/es/LC_MESSAGES/django.po,sha256=Gk9coJCwhogv0JQXBKxzhpGqPbS9Q8-68pl-wNWpA5U,968
|
||||||
|
captcha/locale/fa/LC_MESSAGES/django.mo,sha256=KNb_G6fhiwjjBjWO2u5jVeUgRxJD-KX91vPIjuGKYXg,688
|
||||||
|
captcha/locale/fa/LC_MESSAGES/django.po,sha256=A2OKIY9h8K9CE948-V8UsnfBU6wAXUzlwn_hcHylGDA,1043
|
||||||
|
captcha/locale/fr/LC_MESSAGES/django.mo,sha256=QtmIqpiyxFQjYRBuEn53EDSLdP63jF7M8v9Hz600vLM,594
|
||||||
|
captcha/locale/fr/LC_MESSAGES/django.po,sha256=ppkwxlKg-ATrDFHAKQUfzTbDJyw2h19pHH8YSxitTIk,945
|
||||||
|
captcha/locale/it/LC_MESSAGES/django.mo,sha256=WTElquRQcavHoP7tsERWvCKR6XSxwI3iat8DIFD48xQ,631
|
||||||
|
captcha/locale/it/LC_MESSAGES/django.po,sha256=wTwLs5xko7UQvXRhud143j4Q9zrQUZ29G1GscMyL_ZE,994
|
||||||
|
captcha/locale/ja/LC_MESSAGES/django.mo,sha256=x8M-JHYPyARaiWQd8yGXXsYU86jFIbbWdeTjr_wxEeM,636
|
||||||
|
captcha/locale/ja/LC_MESSAGES/django.po,sha256=Uqbys4Sr96GfYR_Nx6GX2-y6qePmZYrB8SKB4Q4D6QY,1007
|
||||||
|
captcha/locale/nl/LC_MESSAGES/django.mo,sha256=A32eapFCLUVGyYWcDTTfvvYL30AUvkdCRhwAtccpuKU,630
|
||||||
|
captcha/locale/nl/LC_MESSAGES/django.po,sha256=kuQWY1li7KIcF6AXaMZtFBMLHyjLhgpWkg6pPmXpYcA,1024
|
||||||
|
captcha/locale/pl/LC_MESSAGES/django.mo,sha256=RY3o7PISCB5dfi3PNWl1MsJzTCDXZop3l4vfV9xnpFk,680
|
||||||
|
captcha/locale/pl/LC_MESSAGES/django.po,sha256=sGEUv_GUFrlNyozScmtUrN_-ZH4voW8t6CaHWC0rSLo,1107
|
||||||
|
captcha/locale/pt_BR/LC_MESSAGES/django.mo,sha256=Y0dwoL_ndMq0YlAu0IxEoSU76YsOUl0MULUOvJCcaUY,613
|
||||||
|
captcha/locale/pt_BR/LC_MESSAGES/django.po,sha256=5xq-imJL5oNDDDRbQuEkny1bPS4RN3LY8H1GGV2unAs,1025
|
||||||
|
captcha/locale/ru/LC_MESSAGES/django.mo,sha256=PObABAXyLMMPrOM-_J3GqvcGlLN_raQCFuOvNOWvK78,724
|
||||||
|
captcha/locale/ru/LC_MESSAGES/django.po,sha256=7CEb3GZO1v2wh7A2pvx8bz0Pvzj0HI8Xw5reijJzTao,1053
|
||||||
|
captcha/locale/sk/LC_MESSAGES/django.mo,sha256=gWwIkEnmh7MsK0zGVdcDEqnqoNl81D4yu0kHvLra26o,606
|
||||||
|
captcha/locale/sk/LC_MESSAGES/django.po,sha256=1USBxP1l_jvIIHswSylarYJIrryz3F-ZqHlXLOTC-PM,994
|
||||||
|
captcha/locale/sv/LC_MESSAGES/django.mo,sha256=kiovetWFjPSKhhu0lpQ0tNPuySJXiiiOvX0LeliaZiw,631
|
||||||
|
captcha/locale/sv/LC_MESSAGES/django.po,sha256=GiRNXRUeFMK29rMKZW5iAwX9MJO9tJuWMRT5-AlWk_A,991
|
||||||
|
captcha/locale/tr/LC_MESSAGES/django.mo,sha256=Mw0UkeFXwFa_DxVTbfta488L-1pSJ-BW33jx69z2pZU,605
|
||||||
|
captcha/locale/tr/LC_MESSAGES/django.po,sha256=2dSjovuso9lnHyEMC0tFHzRlDh_Njh_VuRjWb4LdEI0,978
|
||||||
|
captcha/locale/uk/LC_MESSAGES/django.mo,sha256=FQoQB-hoVxdAeBQYwxMy_wxD3wXlOLxpwuakWIr5YQs,715
|
||||||
|
captcha/locale/uk/LC_MESSAGES/django.po,sha256=qGaetu4TkmMjCnHocFnN5fmYFqST5NpNgSHb3UH2TFA,1082
|
||||||
|
captcha/locale/zh_CN/LC_MESSAGES/django.mo,sha256=1KxboU5k5qQ0mTleJz7a3iLLhLLajHPAKl5sGwWxGVs,602
|
||||||
|
captcha/locale/zh_CN/LC_MESSAGES/django.po,sha256=jHpBPXBY5bn_IhhJAOj9EZPO8hnw3Uz_uQJvjnPQy9M,1004
|
||||||
|
captcha/locale/zh_Hans/LC_MESSAGES/django.mo,sha256=1KxboU5k5qQ0mTleJz7a3iLLhLLajHPAKl5sGwWxGVs,602
|
||||||
|
captcha/locale/zh_Hans/LC_MESSAGES/django.po,sha256=jHpBPXBY5bn_IhhJAOj9EZPO8hnw3Uz_uQJvjnPQy9M,1004
|
||||||
|
captcha/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||||
|
captcha/management/__pycache__/__init__.cpython-311.pyc,,
|
||||||
|
captcha/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||||
|
captcha/management/commands/__pycache__/__init__.cpython-311.pyc,,
|
||||||
|
captcha/management/commands/__pycache__/captcha_clean.cpython-311.pyc,,
|
||||||
|
captcha/management/commands/__pycache__/captcha_create_pool.cpython-311.pyc,,
|
||||||
|
captcha/management/commands/captcha_clean.py,sha256=w1PTkXEmdyf3xk7Q0uujqSBDaNao2poq-9b7yL751XQ,894
|
||||||
|
captcha/management/commands/captcha_create_pool.py,sha256=tYmZeN9JmBh0HF0Fni2Bt4RNRpRm_LM6U_QMcTWZTL0,1089
|
||||||
|
captcha/migrations/0001_initial.py,sha256=sQj9d3qSatZ0xBF8vOaWj_mCnaOvnQilwUA21XNtQFE,893
|
||||||
|
captcha/migrations/0002_alter_captchastore_id.py,sha256=x5KnL0ZgRJkmPdqDhct2yMyBG9qydQszH4Wy0_arOUY,398
|
||||||
|
captcha/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||||
|
captcha/migrations/__pycache__/0001_initial.cpython-311.pyc,,
|
||||||
|
captcha/migrations/__pycache__/0002_alter_captchastore_id.cpython-311.pyc,,
|
||||||
|
captcha/migrations/__pycache__/__init__.cpython-311.pyc,,
|
||||||
|
captcha/models.py,sha256=a0L5W6Pc_lFSwD8vPV7pX7uhs2xodKE7LNXmealJpdo,2817
|
||||||
|
captcha/templates/captcha/field.html,sha256=8IaHIZudDm3WP2y_AmA8AOsvXg4y6QSWqlOrbA1aGD4,40
|
||||||
|
captcha/templates/captcha/hidden_field.html,sha256=i5aTo_jXDRNr0nhzpQ13fFC_r-n0nE9gLhDzAfuz3ow,72
|
||||||
|
captcha/templates/captcha/image.html,sha256=HMATyrCVuw3RCONGqeY-uxeHRbxGq2E0LjoJmdq0_R0,232
|
||||||
|
captcha/templates/captcha/text_field.html,sha256=V9GfTCtTD4ByEZbuXPMNDL7JH6E-AXgI_P_94ohWSck,131
|
||||||
|
captcha/templates/captcha/widgets/captcha.html,sha256=RHOTJoRIdCheT2tQnf-99_eA_AXJctW9JJfhy4X04AM,314
|
||||||
|
captcha/tests/__init__.py,sha256=PnIM4lAxWFf0dhvr9DobN1qgW7-qmuwdWUwhOoqEFlE,58
|
||||||
|
captcha/tests/__pycache__/__init__.cpython-311.pyc,,
|
||||||
|
captcha/tests/__pycache__/tests.cpython-311.pyc,,
|
||||||
|
captcha/tests/__pycache__/urls.cpython-311.pyc,,
|
||||||
|
captcha/tests/__pycache__/views.cpython-311.pyc,,
|
||||||
|
captcha/tests/tests.py,sha256=RgOQxsnyw3__utoogJNeN2xXBT7uZ87k13AV5kh_fJ4,24079
|
||||||
|
captcha/tests/urls.py,sha256=9YwNDZ8zw_QooLiSn2niXIrJTrJcLXsV8AYI8onXzpw,878
|
||||||
|
captcha/tests/views.py,sha256=DA-zqplkVGd2IQn7wqP5A2aE1z_7EW6G5qeLnYjTbdw,3563
|
||||||
|
captcha/urls.py,sha256=r3e50R7iEd7NSW4p5jxC_TiX40Y3CqVIsksDLHHEO8k,524
|
||||||
|
captcha/views.py,sha256=zKIiAperFrkr9MXsR6Hyh7Xx6AJFHgZUGEvMZSI0188,7409
|
||||||
|
django_simple_captcha-0.5.20.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||||
|
django_simple_captcha-0.5.20.dist-info/LICENSE,sha256=7F9KedNcOJEE-RE8ATQT_b6ISsXIqVKoc03GdlkQzA8,1064
|
||||||
|
django_simple_captcha-0.5.20.dist-info/METADATA,sha256=ZQaTYeyPbeX5qhQeDpoqhhzaeJaMSpyBtin8mRIOuTs,2365
|
||||||
|
django_simple_captcha-0.5.20.dist-info/RECORD,,
|
||||||
|
django_simple_captcha-0.5.20.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||||
|
django_simple_captcha-0.5.20.dist-info/WHEEL,sha256=iYlv5fX357PQyRT2o6tw1bN-YcKFFHKqB_LwHO5wP-g,110
|
||||||
|
django_simple_captcha-0.5.20.dist-info/top_level.txt,sha256=krw23fzzUC0oTgsEakKehDWjBfNzFYPTtC0hVpktXyQ,8
|
@ -0,0 +1,6 @@
|
|||||||
|
Wheel-Version: 1.0
|
||||||
|
Generator: bdist_wheel (0.41.2)
|
||||||
|
Root-Is-Purelib: true
|
||||||
|
Tag: py2-none-any
|
||||||
|
Tag: py3-none-any
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user