pax_global_header00006660000000000000000000000064147573540300014522gustar00rootroot0000000000000052 comment=54c31b874b984041d92af17c3b669296453e646c django-simple-captcha-0.6.2/000077500000000000000000000000001475735403000156615ustar00rootroot00000000000000django-simple-captcha-0.6.2/.flake8000066400000000000000000000000771475735403000170400ustar00rootroot00000000000000[flake8] ignore = E501, W503 exclude = migrations,.venv_*,docs django-simple-captcha-0.6.2/.github/000077500000000000000000000000001475735403000172215ustar00rootroot00000000000000django-simple-captcha-0.6.2/.github/workflows/000077500000000000000000000000001475735403000212565ustar00rootroot00000000000000django-simple-captcha-0.6.2/.github/workflows/test.yml000066400000000000000000000071061475735403000227640ustar00rootroot00000000000000name: Test Django Simple Captcha on: [push, pull_request] jobs: build: runs-on: ubuntu-latest strategy: max-parallel: 4 matrix: python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | sudo apt-get install gettext python -m pip install --upgrade pip wheel pip install tox tox-gh-actions - name: Test with tox run: tox windows: runs-on: windows-latest strategy: matrix: python-version: - '3.12' steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install and upgrade packaging tools run: python -m pip install --upgrade pip setuptools wheel - run: python -m pip install tox tox-gh-actions - name: Run tests run: tox docs: runs-on: ubuntu-latest strategy: max-parallel: 4 matrix: python-version: - '3.12' steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install and upgrade packaging tools run: python -m pip install --upgrade pip setuptools wheel - run: python -m pip install tox tox-gh-actions - name: Run tests run: tox -e docs coverage: runs-on: ubuntu-latest strategy: max-parallel: 4 matrix: python-version: - '3.12' steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install audio dependencies run: sudo apt-get install sox flite - name: Install and upgrade packaging tools run: python -m pip install --upgrade pip setuptools wheel - run: python -m pip install tox tox-gh-actions - name: Run tests run: tox -e coverage env: CAPTCHA_FLITE_PATH: '/usr/bin/flite' CAPTCHA_SOX_PATH: '/usr/bin/sox' gettext: runs-on: ubuntu-latest strategy: max-parallel: 4 matrix: python-version: - '3.12' steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: sudo apt-get install gettext - name: Install and upgrade packaging tools run: python -m pip install --upgrade pip setuptools wheel - run: python -m pip install tox tox-gh-actions - name: Run tests run: tox -e gettext flake8: runs-on: ubuntu-latest strategy: max-parallel: 4 matrix: python-version: - '3.12' steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install and upgrade packaging tools run: python -m pip install --upgrade pip setuptools wheel - run: python -m pip install tox tox-gh-actions - name: Run tests run: tox -e flake8 django-simple-captcha-0.6.2/.gitignore000066400000000000000000000002441475735403000176510ustar00rootroot00000000000000dist build *.pyc *.egg-info docs/_build/ .venv* testproject/django-simple-captcha.db testproject/.coverage testproject/coverage.xml testproject/htmlcov/ .tox .idea django-simple-captcha-0.6.2/.pre-commit-config.yaml000066400000000000000000000012211475735403000221360ustar00rootroot00000000000000repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.0.1 hooks: - id: fix-encoding-pragma args: ['--remove'] - id: debug-statements - id: check-merge-conflict - id: check-ast - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/timothycrosley/isort rev: 5.12.0 hooks: - id: isort additional_dependencies: [toml] - repo: https://github.com/charliermarsh/ruff-pre-commit # Ruff version. rev: "v0.2.2" hooks: # Run the linter. - id: ruff args: [ --fix ] # Run the formatter. - id: ruff-format django-simple-captcha-0.6.2/.readthedocs.yaml000066400000000000000000000005161475735403000211120ustar00rootroot00000000000000version: 2 build: os: ubuntu-22.04 tools: python: "3.10" # You can also specify other tool versions: # nodejs: "16" # Build documentation in the docs/ directory with Sphinx sphinx: configuration: docs/conf.py # Dependencies required to build your docs python: install: - requirements: docs/requirements.txt django-simple-captcha-0.6.2/CHANGES000066400000000000000000000244001475735403000166540ustar00rootroot00000000000000Version History =============== Version 0.6.2 ------------- * Make DRF dependency optional Version 0.6.1 ------------- * Fixed docs and test configuration * Delete leftover temporary audio files (#224, #225, #226 thanks @dawidratynski and @A-dead-pixel) * Test against Django 5.1 and 5.2 * Add ability to control color of each character (PR #235, thanks @mheidarian) * Add support for Django REST Framework (PR #236, thanks @michalwrona01) * Audio CAPTCHA: try to detect the sample rate of the generated WAV file prior to merge (Issue #196) Version 0.6.0 ------------- * Only Django versions 4.2 or above are now supported * Removed the old rendering methods that were deprecated in 2017. Version 0.5.20 -------------- * Still support Django 3.2 (#222, thanks @petrklus) Version 0.5.19 -------------- * SECURITY ISSUE: reset the random seed after an image was generated (#221, thanks @ibuler) Version 0.5.18 -------------- * Fix some typos in documentation (#210, thanks @stweil) * Test against Django 4.2 * Stopped testing Django < 3.2 * BaseCaptchaTextInput should set autocomplete=off on the hashkey HiddenInput (#201, thanks @eerotal) * Test against Django 4.2a * Fix some deprecation warnings in Pillow 9.2+ * Removed old unused conditional imports * Format code with pre-commit and black Version 0.5.17 -------------- * English translation created from Spanish ones (#209, thanks @tpazderka) Version 0.5.16 -------------- * Adds a migration missing from 0.5.15 (#208, thanks @atodorov) Version 0.5.15 -------------- * Updated test matrix, drop tests against Python3.6, test against Python3.9 * Remove dependency on six * Test against Django 4.0a1 * Test with Python 3.10 (Django 3.2 and Django 4.0) * Remove warning for django 3.2 (#206, thanks @MiStErLu) Version 0.5.14 -------------- * Generate deterministic CAPTCHA images for each Captcha instance (#194, thanks @mjnaderi) * New setting CAPTCHA_2X_IMAGE to disable the double resolution image option (#195, thanks @mjnaderi) * Test against Django 3.2a Version 0.5.13 -------------- * Fix DeprecationWarnings on Django 3 related to unicode (#173, thanks @jannh) * Applied black to the codebase and ignored W503 * tox: remove superfluous basepython statement (#172, thanks @devkral) * i18n: add Persian (fa) language translation files (#190, thanks @mavenium) * Test Django 2.2, 3.0 and 3.1 on Python 3.6, 3.7 and 3.8 (#171, thanks @devkral) * Fix deprecation warnings on Django 3.1 (#186, thanks @ jannh and @TheBuky) * Travis fixes and test on ppc64le arch (#192 and #191, thanks @kishorkunal-raj) Version 0.5.12 -------------- * Doc: Update url include line for Django (#164, thanks @jpic) * Doc: Fixed a typo (#162, thanks @RubenGarcia) * Initial fixes and test against Django 3.0 (#166, #165. Thanks @devkral) * Don't open dictionary file in binary mode (#167, thanks @grasshoppermouse) Version 0.5.11 -------------- * Fix: CAPTCHA_TEST_MODE was broken. (#163, thanks @ohlr for reporting) Version 0.5.10 -------------- * Test against Django 2.2a1 * Docs: Grammar correction (#160, thanks @DanAtShenTech) * Fix: Add '+' to text replacement for audio support (#157, thanks @geirkairam) * I18N: Added Swedish translation (#155, thanks @stefannorman) * Docs: Provide an example of custom field template (#158, thanks @TheBuky) Version 0.5.9 ------------- * Add missing Jinja2 templates in the pypi packages. Version 0.5.8 ------------- * Add support for Jinja2 templates (Issue #145, PR #146, thanks @ziima) * Cleanup, drop dependency on South (#141, #142 thanks @ziima) Version 0.5.7 ------------- * Use templates for rendering of widgets (Issue #128, #134, PR #133, #139, thanks @ziima) * Always defined audio context variable (PR #132, thanks @ziima) * Test against Django 2.1a * Updated AJAX update docs (PR #140, thanks @CNmanyue) * Fixed a typo in a variable name (PR #130, thanks @galeo) Version 0.5.6 ------------- * Updated render method to adapt for Django 2.1 (PR #120, thanks @skozan) * Improved compatibility with Django 2.0, tests against Django 2.0a1 (PR #121, thanks @Kondou-ger) * Dropped support for PIL (use Pillow instead) * Updated documentation (Fixes #122, thanks @claudep) * Test against Django 2.0b1 * Return a Ranged Response when returning WAV audio to support Safari (Fixes #123, thanks @po5i) * Optionally inject brown noise into the generated WAV audio file, to avoid rainbow-table attacks (Fixes #124, thanks @appleorange1) * Test against Django 2.0 Version 0.5.5 ------------- * I messed the 0.5.4 release, re-releasing as 0.5.5 Version 0.5.4 ------------- * Removed a couple gremlins (PR #113, thanks @Pawamoy) * Added autocapitalize="off", autocorrect="off" and spellcheck="false" to the generated field (PR #116, thanks @rdonnelly) * Test against Django 1.11 * Drop support of Django 1.7 ("it'll probably still work") Version 0.5.3 ------------- * Ability to pass a per-field challenge generator function (Fixes #109) * Added a feature to get captchas from a data pool of pre-created captchas (PR #110, thanks @skozan) * Cleanup to remove old code handling timezones for no longer supported Django versions * Fix for "Size must be a tuple" issue with Pillow 3.4.0 (Fixes #111) Version 0.5.2 ------------- * Use any multiplication uperator instead of "*". (Fixes #77 via PR #104, thanks @honsdomi and @isergey) * Test against Django 1.10 Version 0.5.1 ------------- * Fine tuning MANIFEST.in * Prevent testproject from installing into site-packages Version 0.5.0 ------------- * Adds missing includes in MANIFEST.in Version 0.4.7 ------------- * Supported Django versions are now 1.7, 1.8 and 1.9 * Trying to fix the TravisCI build errors * Use Django templates to render the individual fields, as well as the assembled Captcha Field (Issue #31) Version 0.4.6 ------------- * Fixes an UnicodeDecodeError which was apparently only triggered during testing on TravisCI (I hope) * Support for Django 2.0 urlpatterns syntax (PR #82, Thanks @R3v1L) * settings.CAPTCHA_FONT_PATH may be a list, in which case a font is picked randomly (Issue #51 fixed in PR #88, Thanks @inflrscns) Version 0.4.5 ------------- * Test with tox * Test against Django 1.8 final * Added ability to force a fixed image size (PR #76, Thanks @superqwer) Version 0.4.4 ------------- * Added id_prefix argument (fixes issue #37) Version 0.4.3 ------------- * Add null noise helper (Thanks @xrmx) * Test against Django 1.7b4 * Added Spanish translations (Thanks @dragosdobrota) * Massive cleanup (pep8, translations) * Support for transparent background color. (Thanks @curaloucura) * Support both Django 1.7 migrations and South migrations. Please note, you *must* add the following to your settings, if you are using South migrations and Django 1.6 or lower. * Make sure autocomplete="off" is only applied to the text input, not the hidden input (Issue #68, thanks @narrowfail) * Fixed some grammar in the documentation. (Thanks @rikrian) * Return an HTTP 410 GONE error code for expired captcha images, to avoid crawlers from trying to reindex them (PR #70, thanks @joshuajonah) * Fixed title markup in documentation (#74, thanks @pavlov99) * Test against Django 1.7.1 Version 0.4.2 ------------- * Added autocomplete="off" to the input (Issue #57, thanks @Vincent-Vega) * Fixed the format (msgfmt -c) of most PO and MO files distributed with the project * Added Bulgarian translations. (Thanks @vstoykov) * Added Japanese translations. (Thanks, Keisuke URAGO) * Added Ukrainian translations. (Thanks, @FuriousCoder) * Added support for Python 3.2. (Thanks, @amrhassan) Version 0.4.1 ------------- * Dropped support for Django 1.3 * Fixed support of newer versions of Pillow (2.1 and above. Pillow 2.2.2 is now required) Thanks @viaregio (Issue #50) Version 0.4.0 ------------- * Perform some tests at package installation, to check whether PIL or Pillow are already installed. (Issue #46) * Added Slovak translations. (Thanks @ciklysta) Version 0.3.9 ------------- * Run most tests both with a regular Form and a ModelForm, to avoid regressions such as Issue #40 * Handle the special case where CaptchaFields are instantiated with required=False (Issue #42, thanks @DrMeers) * Fixed a misspelled setting, we now support both spellings, but the docs suggest the correct one (Issue #36, thanks @sayadn) * Added Django 1.6b to testrunner and adapted the test cases to support Django 1.6's new test discovery * Added German translations. (Thanks @digi604) * Frozen the version of Pillow to 2.0.0, as 2.1.0 seems to be truncating the output image -- Issue #44, Thanks @andruby * Added Polish translations. (Thanks @stilzdev) Version 0.3.8 ------------- * Fixed a critical bug (Issue #40) that would generate two captcha objects, and the test would always fail. Thanks @pengqi for the heads-up. Version 0.3.7 ------------- * Improved Django 1.5 and Django HEAD (1.6) compatibility (thanks @uruz) * Python3 compatibility (requires six and Pillow >= 2.0) * Added zh_CN localization (thanks @mingchen) * Make sure the generated challenge is a string type (the math challenge was probably broken -- Issue #33, thanks @YDS19872712) * Massive cleanup and refactoring (Issue #38, thanks @tepez) * Test refactoring to test a couple generators that weren't tested by default Version 0.3.6 ------------- * Django 1.5 compatibility (only affects tests) * Italian localization (thanks @arjunadeltoso) * Russian localization (thanks @mikek) * Fixed issue #17 - Append content-length to response (thanks @shchemelevev) * Merged PR #19 - AJAX refresh of captcha (thanks @artofhuman) * Merged PR #22 - Use op.popen instead of subprocess.call to generate the audio CAPTCHA (thanks @beda42) * Fixed issue #10 - uniformize spelling of "CAPTCHA" (thanks @mikek) * Fixed issue #12 - Raise error when try to initialize CaptchaTextInput alone and/or when try to initialize CaptchaField with widget keyword argument (thanks @vstoykov) * Merged PR #15 - Allow a 'test mode' where the string 'PASSED' always validates the CAPTCHA (thanks @beda42) * Dutch translation (thanks @leonderijke) * Turkish translation (thanks @gkmngrgn) Version 0.3.5 ------------- * Fixes issue #4: Fixes id_for_label malfunction with prefixed forms (thanks @lolek09) Version 0.3.4 ------------- * Fixes issue #3: regression on Django 1.4 when USE_TZ is False Version 0.3.3 ------------- * Django 1.4 Time zones compatibility * PEP 8 love Version 0.3.2 ------------- * Added a test project to run tests * Added South migrations django-simple-captcha-0.6.2/LICENSE000066400000000000000000000020501475735403000166630ustar00rootroot00000000000000Copyright (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. django-simple-captcha-0.6.2/MANIFEST.in000066400000000000000000000007461475735403000174260ustar00rootroot00000000000000include MANIFEST.in include LICENSE include README.rst include CHANGES include tox.ini include .pep8 recursive-include captcha/fonts * recursive-include captcha/locale * recursive-include captcha/templates * recursive-include captcha/jinja2 * recursive-include testproject * recursive-include docs * exclude testproject/django-simple-captcha.db prune .tox prune docs/_build prune htmlcov global-exclude *pyc global-exclude coverage.xml global-exclude .DS_Store global-exclude .coverage django-simple-captcha-0.6.2/README.rst000066400000000000000000000025551475735403000173570ustar00rootroot00000000000000********************* 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 4.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 `_. django-simple-captcha-0.6.2/captcha/000077500000000000000000000000001475735403000172645ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/__init__.py000066400000000000000000000002131475735403000213710ustar00rootroot00000000000000VERSION = (0, 6, 2) def get_version(): "Return the version as a human-format string." return ".".join([str(i) for i in VERSION]) django-simple-captcha-0.6.2/captcha/conf/000077500000000000000000000000001475735403000202115ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/conf/__init__.py000066400000000000000000000000001475735403000223100ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/conf/settings.py000066400000000000000000000062341475735403000224300ustar00rootroot00000000000000import os from django.conf import settings from django.utils.module_loading import import_string 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_LETTER_COLOR_FUNCT = getattr(settings, "CAPTCHA_LETTER_COLOR_FUNCT", None) 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_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 elif isinstance(string_or_callable, str): return import_string(string_or_callable) 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 [] def get_letter_color(index, challenge): if CAPTCHA_LETTER_COLOR_FUNCT: return _callable_from_string(CAPTCHA_LETTER_COLOR_FUNCT)(index, challenge) return CAPTCHA_FOREGROUND_COLOR django-simple-captcha-0.6.2/captcha/fields.py000066400000000000000000000141131475735403000211040ustar00rootroot00000000000000from 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.urls import NoReverseMatch, reverse from django.utils import timezone 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().__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, id_prefix=None, generator=None, # output_format=None, ): self.id_prefix = id_prefix self.generator = generator super().__init__(attrs) def build_attrs(self, *args, **kwargs): ret = super().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().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().get_context(name, value, attrs) context["image"] = self.image_url() context["audio"] = self.audio_url() return context def render(self, name, value, attrs=None, renderer=None): self.fetch_captcha_store(name, value, attrs, self.generator) extra_kwargs = {} extra_kwargs["renderer"] = renderer return super().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( id_prefix=kwargs.pop("id_prefix", None), generator=kwargs.pop("generator", None), ), ) super().__init__(fields, *args, **kwargs) def compress(self, data_list): if data_list: return ",".join(data_list) return None def clean(self, value): super().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 django-simple-captcha-0.6.2/captcha/fonts/000077500000000000000000000000001475735403000204155ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/fonts/COPYRIGHT.TXT000066400000000000000000000134761475735403000224010ustar00rootroot00000000000000Bitstream 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. django-simple-captcha-0.6.2/captcha/fonts/README.TXT000066400000000000000000000005011475735403000217470ustar00rootroot00000000000000Contained 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 django-simple-captcha-0.6.2/captcha/fonts/Vera.ttf000066400000000000000000002006141475735403000220340ustar00rootroot00000000000000OS/2_cpVPCLTъ^6cmaplXcvt 9fpgm&`gaspH glyf tA&~hdmx4!Hhead݄T6hheaEoL$hmtx Ǝ0kernRՙ-loca=maxpG:, nameټȵpostZ/prep; h::_:: dM0l   p t  &   Y &  &   c . 5 `  s 0 & {Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved.Bitstream Vera SansBitstreamVeraSans-RomanRelease 1.10Copyright (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.http://www.bitstream.comCopyright (c) 2003 by Bitstream, Inc. All Rights Reserved.Bitstream Vera SansBitstreamVeraSans-RomanRelease 1.10Copyright (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.http://www.bitstream.com5fqu-J3T99NR7s`s3VV9s3D{o{RoHT3fs +b-{T#\q#H99`#fy```{w``b{{Rffw;{J/}oo5jo{-{T7fD)fs@%2%%A:B2SAS//2ݖ}ٻ֊A}G}G͖2ƅ%]%]@@%d%d%A2dA  d   A(]%]@%..%A  %d%@~}}~}}|d{T{%zyxw v utsrqponl!kjBjSih}gBfedcba:`^ ][ZYX YX WW2VUTUBTSSRQJQP ONMNMLKJKJIJI IH GFEDC-CBAK@?>=>=<=<; <@; :987876765 65 43 21 21 0/ 0 / .- .- ,2+*%+d*)*%)('%(A'%&% &% $#"!! d d BBBdB-B}d       -d@--d++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++, %Id@QX Y!-,%Id@QX Y!-,  P y PXY%%# P y PXY%-,KPX EDY!-,%E`D-,KSX%%EDY!!-,ED-ff@ /10!%!!fsr)5 @@ <2991/0K TX @ 878Y P ]%3#3#5qeM@1<20KTKT[X@878Y@0 @ P ` p ]#!#o$++`@1      91/<<<<<<<2220@   ]!! !3!!!!#!#!5!!5!T%Dh$ig8R>hggh`TifaabbNm!(/@U" '&( /)/))/B" ) *!#*- ) " & 0<<<1/299990KSX99Y"K TX0@00878YK TKT[KT[X000@878Y#.'5.546753.'>54&dijfod]SS\dtzq{---@A$*.U# jXV`OnZXhq) #'3@6$%&%&'$'B .$ &($4'!%   ! + 1 49912<0KSXY"K TK T[K T[KT[KT[K T[X4@44878Y"32654&'2#"&546"32654&%3#2#"&546WccWUccUVcbWWcd1Zܻۻa ۻۼ 0@      !         B  (('+'$ .  .'.'!!199999991/9990KSX99999999Y"2]@ " ) **&:4D ^YZ UZZY0g{ "-  ' (   2'') #**(/2; 49?2J LKFO2VZ Y UY\_2j i`2uy z 2229]]3267 >73#'#"5467.54632.#"[UԠ_I{;B h]hΆ02޸SUWDi;#QX?@Yr~YW׀c?}<$$/1oX3goB@ 10KTKT[X@878Y@ @P`p]#o+{ O@  29910KTX@878YKTX@878Y#&547{>;o @ <99103#654<:=JN@,       <2<2991<22990%#'%%73%g:r:g:PrPbybcy #@   <<1/<<0!!#!5!-Ө-Ӫ--@ 1073#ӤR@d10!!d1/073#B-@B/9910KSXY"3#m #@  10"32'2#"  P3343ssyzZ K@B  1/20KSXY"KTX  @878Y]7!5%3!!JeJsHHժJ@'B   91/20KSX9Y"KTKT[KT[X@878Y@2UVVzzvtvust]]%!!567>54&#"5>32Ls3aM_xzXE[w:mIwBC12\ps({@.    #)&  )99190KTKT[X)@))878Y@ daa d!]!"&'532654&+532654&#"5>32?^jTmǹSrsY %Đ%%12wps{$& Ѳ|d @   B    <291/<290KSXY"K TK T[X@878Y@* *HYiw+&+6NO O Vfuz ]] !33##!55^%3`du@#    190KTKT[X@878YKTX@878Y!!>32!"&'532654&#",X,$^hZkʭQTժ 10$& $X@$  "% " !%190@]]"32654&.#">32# !2 LL;kPL;y$&W]ybhc@B991/0KSXY"KTX@878Y@X9Hg]]!#!3V+ #/C@% '-'0 $*$ !0991990"32654&%&&54632#"$54632654&#"HŚV г "Əُattt$X@# %!"" %190@]]7532#"543 !"&2654&#"LK:lL>$& V\s[#@<21/073#3### %@  <2103#3#ӤR#٬@^M@*B$#29190KSXY" 5Ѧ`@ #<210!!!!^O@+B$#<9190KSXY"55//m$p@+$     &%99991/9990K TX%@%%878Yy z z ]%3##546?>54&#"5>32ſ8ZZ93lOa^gHZX/'eVY5^1YnFC98ŸLVV/5<4q L@2  L4307$7CM34( (+(I+*(I,=M<9912990K TK T[KT[KT[KT[XMMM@878Y@ NN/N?N]32654&#"#"&5463253>54&'&$#"3267#"$'&5476$32|{zy!orqp ˘s'6@   0210].# !267# !2'ffjzSb_^^_HHghG.@   2 99991/0`]3 !%! )5BhPa/w.,~ .@   21/0 ]!!!!!!9>ժF# )@ 21/0 ]!!!!#ZpPժH7s9@ 43 1990%!5!# !2&&# !26uu^opkSUmnHF_`%; ,@ 8  221/<20P ]3!3#!#"d+991/0KTX@878Y@ 0@P`]3#+f M@  9 991990KTX  @878Y@ 0 @ P ` ]3+53265M?nj @(B  291/<290KSXY"]@ ((764GFCUgvw    (+*66650 A@E@@@ b`hgwp  ,]q]q3! !#3wH1j%@ :1/0@ 0P]3!!_ժ @4  B    >  91/<290KSXY"p]@V   && & 45 i|{y   #,'( 4<VY ej vy ]]! !###-}-+3 y@B6 991/<2990KSXY" ]@068HGif FIWXeiy ]]!3!#j+s #@  310"32' ! ':xyLHH[[bb:@   ? 291/0@ ?_]32654&#%!2+#8/ϒs R@*  B     39991990KSX9Y""32#'# ! '? !#y;:xLHHab[T@5  B    ?  299991/<9990KSX9Y"@]@Bz%%%&'&&& 66FFhuuw]]#.+#! 32654&#A{>ٿJx~hb؍O'~@<    B %( "-"(9999190KSX99Y")])/)O)].#"!"&'532654&/.54$32Hs_wzj{r{i76vce+ٶ0/EF~n|-&J@@@1/20K TX@878Y@  @ p ]!!#!ժ+)K@   8A1299990KTX@878Y]332653! ˮ®u\*$h@'B91/290KSXY"P]@b*GGZ} *&&))% 833<<7HEEIIGYVfiizvvyyu)]]!3 3J+D {@I      B     91/<2290KSXY"]@  ($ >>4 0 LMB @ Yjkg ` {|      !   # $ %  <:5306 9 ? 0FFJ@E@BBB@@ D M @@XVY Pfgab```d d d wv{xwtyywpx   []]3 3 3# #D:9:9+=; ]@F      B    91/<290KSXY"K TK T[KT[X  @878Y@ '' 486 KX[fkww       &()&(' ) 54<;:;4 4 8 ? H O X _ eejjhiil l xyyx}  x   @]]3 3 # #su \Y+3{@(B@@ 91/290KSXY" ]@<5000F@@@QQQe &)78@ ghxp ]]3 3#f9\ @BB 991/0KSXY"K TK T[X @ 878Y@@ )&8HGH    / 59? GJO UYfio wx ]]!!!5!sP=g՚oXS@C210K TX@878YKTKT[X@878Y!#3!XB-@B/9910KSXY"#mo<@C<10KTKT[X@878Y!53#5oXޏ@ 91290##HHu-10!5f1@ D10K TKT[X@878Y #ofv{-{ %@'   #   E&22991/9990@n0000 0!0"?'@@@@ @!@"PPPP P!P"P'p' !"'''000 0!@@@ @!PPP P!``` `!ppp p! !]]"326=7#5#"&5463!54&#"5>32߬o?`TeZ3f{bsٴ)Lfa..'' 8@  G F221/0`]4&#"326>32#"&'#3姒:{{:/Rdaadq{?@  HE210@ ].#"3267#"!2NPƳPNM]-U5++++$$>:#qZ8@G E221/0`]3#5#"3232654&#":||ǧ^daDDaq{p@$   KE9190@)?p?????,// , ooooo ]q]!3267# 32.#" ͷjbck)^Z44*,8 Cė/p@     L<<991/22990K TX@878YKTX@878Y@P]#"!!##535463cM/ѹPhc/яNqVZ{ (J@#  &#' & G E)221/990`***]4&#"326!"&'5326=#"3253aQQR9||9=,*[cb::bcd4@  N  F21/<90`]#4&#"#3>32d||Bu\edy+@F<21/0@  @ P ` p ]3#3#`Vy D@   O  F<2991990@ @P`p]3+532653#F1iL`a( @)B F 291/<90KSXY" ]@_ ')+Vfgsw    ('(++@ h` ]q]33 ##%kǹi#y"F1/0@ @P`p]3#{"Z@&   PPF#291/<<<290@0$P$p$$$$$$$ ]>32#4&#"#4&#"#3>32)Erurw?yz|v\`gb|d{6@  N  F21/<90`]#4&#"#3>32d||Bu\`edqu{ J@  QE10@#?{{   {  {]"32654&'2#"s98V{>@ GF2210@ `]%#3>32#"&4&#"326s:{{8 daaqVZ{ >@   GE2210@ `]32654&#"#"3253#/s:||:/daDDadJ{0@    F21/90P].#"#3>32JI,:.˾`fco{'@<  S  SB %( R"E(9999190KSX99Y"']@m   . , , , ; ; ; ; $( ( *//*(() )!$'      '/)?)_))))))]]q.#"#"&'532654&/.54632NZb?ĥZlfae@f?((TT@I!*##55YQKP%$78@  F<<2991/<2990]!!;#"&5#53w{KsբN`>X`6@    NF21/290`]332653#5#"&||Cua{fc=`@'B91/290KSXY"K TX@878YKTKT[X@878Y@Hj{  &&)) 55::0FFIIFH@VVYYPffiigh`ut{{uz>]]3 3#=^^\`TV5` @IU U U U   B     91/<2290KSXY"K TKT[KT[KT[K T[X  @878YK TK T[KT[X @ 878Y@" 5 IIF @ [[U P nnf yy          %%#'!%""%' $ ! # 9669 0FHF@B@@@D D D @@VVVPQRRPS T U cdejejjjn a g ouuy}x}zzxy  { v } @/   y]]333# #V`jjj;y` Z@F      B   91/<290KSXY"K TKT[KT[KT[X  @878YKTX @ 878Y@   & =1 UWX f vzvt        )&% * :9746 9 0 IFE J @ YVYYWVYVV Y P o x  /]] # # 3 dkr))`HJq=V`@C        B     9129990KSX2Y"K TKT[X@878YKTX@878Y@     # 5 I O N Z Z j        '$$  )( % $ $ ' ** 755008 6 6 8 990A@@@@@@@@B E G II@TQQUPPVUVW W U U YYPffh ii`{xx   e]]+5326?3 3N|lLT3!;^^hzHTNlX` @B 2991/0KSXY"K TK T[X @ 878YKTX  @878Y@B&GI  + 690 @@E@@CWY_ ``f``b ]]!!!5!qjL}e`ۓ%$@4 %   !  % $  C %<<29999999199999990K TX%%%@878Y&]#"&=4&+5326=46;#"3>l==k>DV[noZVtsݓXX10#$@6%   #%#C %<2<9999999199999990K TX%@%%878YKTX%%%@878Y&]326=467.=4&+532;#"+FUZooZUF?l>>l?VWstݔ1#@  1990#"'&'&'&#"56632326ian ^Xbian ^V1OD;>MSOE<>LhN'$uhm !@T   !!  ! !!!B     !  VV!"2299999991/<9990KSXY" #]@  s P#f iu {yyv v!# ]]4&#"326!.54632#!#TY?@WX??Y!X=>sr?<҈_Z?YWA?XXN)sIsrFv)su''&-k'(u3^'1usN'2'u)N'8u{-f'DR{-f'DCR{-f'DR{-'DR{-7'DR{-'DRqu{'Fqf'Hqf'HCqf'Hq'Hof'f'C\f'F'd7'Qquf'Rsquf'RCsquf'Rsqu'Rsqu7'RsXf'X{Xf'XC{Xf'X{X'X{9; '@  YW Y <<1<203!!#!5!oo\]u=  @  Z[Z10"32654&'2#"&546PnnPPnoO@v+..ooPOmmOOp1.-rB#!Q@+     "  "<<<221<9990%&&'667#&73JDFHAMf fIX⸹)**'# 32!b`@!    <<1/2<2990K TX@878Y66].#"!!!!53#535632NL=ty-=))׏/я\= >@54&.#"#"&'532654/.5467.54632{?>?>S8alӃ\]>9̭IXW:fqր][;;ȦI.Z.L-[.K''PGZsweZ54m@''TLf{xf[1,pE3!   \ 104632#"&3~|}}||};9 %@]] 91290!###&&54$yfNݸ/@0-'!  **.  !' $'$-F099991/990@@'(     ! "&  : :!MM I!I"jj  ]]4632#"&'532654&/.5467.#"#:A9`@IPAtx;e\`Wqqs`/Q*%jd_[?T>7;[gp/8L`@6EBC?2H09JC 9 $HE301BKL?gwyVpMI`3D/IC@&=>:A$104G$ 7aD=0^* D^ J21/02#"$'&5476$"32676654&'&&&&#"3267#"&54632mmllmmmmllmm^^``^^⃄^]]^\^BB@zBCFInmmmmnnmmmmng^^^傁^^__^]⃅]^^! "'F >@!    b b cbc91<<2<<903#######5Jq7rqr/B^^sRf1@ D10K TKT[X@878Y3#fF)@dd1<20K TK T[X@878YK TK T[KT[KT[X@878YKTKT[X@878Y@````pppp]3#%3#^y'>@"     <291<2<<990!!!!!'7!5!7!}/H{};fըfӪH@9  B     <291/<0KSXY"]@gww  ]!!!!!!#!59=qժF՞f +@< +,  )&  *&& &,+,* # )#3,99999999199999990@*WZWU!je!{vu! FYVjddj(|svz( ]] 324&'.#"&5!27!"&''3>_'y=_''NOy;WfNPƀ[gX@CHp@CpDfbMKYg[KKX /@- !$'!!0 $*0999919990@     $$$   $$ $ ***///***55500055 5 :::???:::EEE@@@EE E JJJOOOJJJV !"&'()]]32654&#".#"326#"&54632>32#"&1TevYR1UfvYRF^_HDa^/XZie7XXjeߦ~᧯w .@     <2<21/<<0!!#!5!!!-Ө-}} T@.B $# <2291/90KSXY" 5!!@po V@/B$ # <<291/90KSXY"55!5AǪR@F  B     fe f e<2299991/2<2<290KSXY"K TX@878Y@(' ' ')((79  ]]!#!5!5'!5!3 3!!!c`Tþ{yT9{3{JD{3V` M@%  !   NF!2912<990"`""]3326533267#"&'#"&'#% )I#ER2bf*V H<9 NPOONNh-)b@'! '!* $$*9991990K TK T[KT[KT[KT[X*@**878Y>54&#"#"&54632#"&54324&#"32IH7$$0e՘ݢe WOmVPmmWKt,>bFأ[t}t{w; ]@    91990@0QVPZ spvupz  Z pp{ t  ]]!! !!5 7AJI3!wq@gg120!#!# }/#@1 " $ #" #h#$9999991/<229990K TX$$$@878Y@V             ##(]]#3267#"&5467!##"#>3!i/7.%7vy"Pµ)6< yJ\:1fd.xo@E}/%&@  & iji&1026732#"&'&&#"#"&546327j Pd@7*8  kOeD=!0 l9TA6?&#Hn!bSA8?Ss;)_@3(%%  * "(kl"k *22999199990!!#5#"&5463354&#"56632"32655P,]uu>DIE~bRhP{@p?Dq[[""CO@Mr`d.@  klk 9910!!2#"&546"32654&PXγгi~hi}|P{ݿܾsN@@"   mm  9991/<20%!5654#"!5!&5! Dz?1/aL"a*>w؍{o{3>@C'-%= 4%:.-*1 %?47&%7& =&-7"E?<9999912<<29990@0+0,0-0.0/00@+@,@-@.@/@0P+P,P-P.P/P0+0@@@@@@@@@??? ??0,0-0.0/@,@-@.@/P,P-P.P/ooo oo`,`-`.`/p,p-p.p/,-./]q].#">32!3267#"&'#"&5463!54&#"5>32"326=DJԄ ̷hddjMI؏`TeZ߬o0Z^Z55*,ywxx..''`f{bsٴ)H +@<+,&  )&  *&& &,+,* # #Q)E,22999999199999990@p(?-YVUV jf!{    { z{ {!"#$%{&%--&YVUZ(ifej(ztvz($$]] 32654&'.#".5327#"&'')gA\*g>}66]C_56`?`!*(Ou))Hn.Mw834OMx43N $@/  !# #%" " "!& %999919990KTKT[KT[X%%%@878Y@ ttttv]33267#"&546?>7>5#537ZZ:3mN`^gIYX0&DeWX5^1YnFC98ŸLVV/5<65 b@ <2991/0K TX @ 878YKTKT[KT[X  @878Y P ]#53#3+e^@ 10!#!^=} *@    91903##'%\sB}}`s-Pb;V#@@   B   !$  $912299990KSX29Y"K TX$$$@878Y.#"!!#"&'53267#5!>32&P,`r<::d/4a/am"?$Ɨ5dzɏ!!J;?@.9*-" *19" <-<<219999990#"'&'&'&#"56632326#"'&'&'&#"56632326ian ^Xbian ^Vgian ^Xbian ^VoNE;=LTNE;=KڲOE;=LSNE;=K`8@91/90@cmpxyvn]] !3!^DC?%# @I    B   o o n<2991<2990KSXY"55%-+#-+#RRH# @I  B   o op<<991<2990KSXY"5%5+-+-#^R^  ^R^   #@   1/<<220%3#%3#%3#hk'$uh^'$us^'2'us ;@   299991/220!!!!! !# !39OAg@AժF|pm|q{'3@1 . ("%4"1 K1 Q+E499912<2290@%?5_5p55555????? ooooo ]q].#"!3267#"&'#"32>32%"32654& H ̷jbdjQGьBN5Z44*,nmnm98olkp݇y/10!!yy/10!!ym '@   1<20#53#53ӤRӤR??m '@   1<203#%3#ӤRӤRլ@@@ 10#53ӤR?@ q103#ӤR՘?o )@ r <<103#3#!!oA#u"@91990  9%-=V'\^N'<su+@B10KSXY"3#-\^R#/@I -'! - -'!0 *$0* $ $(st*(s099999999919999999907'#"&''7&&5467'766324&#"326{r%$&(r;t=:x=q%%&&s7t@?s9q(&%%s>v:@t8s'%$|pprs#G@%Bon29190KSXY"5s-+#R#I@&Bop<9190KSXY"5+-#^R^  /J@(   L<2<2991/<22990K TX@878YKTX@878Y@0P]]#!##53546;#"3#JcM`/яNPhc/J@!    L<<991/<22990K TX@878YKTX@878Y@0P ]!#!"!!##53546JcM/ѹ{Phc/яN9;>@   Y W Y <<2<<2122220%!#!5!!5!3!!!oooo\\HF103#F@ 10%3#ӤR@m '@    1<20%3#%3#ӤRfӤR@@q L #'3?K@D$%&%&'$'B@ .(F4 :&$L%IC'1+C =  1 =I 7+ ! L9912<<2220KSXY"KTK T[K T[K T[K T[KT[XL@LL878Y"32654&'2#"&5462#"&546!3#"32654&2#"&546"32654&WddWUccUt%ZVcbWWcdWccWUccܻۻۻۼܻۻhm'$um'(uhk'$uN'(uk'(uk',/u`m',/uXN',/u;k',/usk'2'usm'2'usk'2'u)k'8u)m'8u)k'8uy` F1/0@ @P`p]3#`?f7@ u91290K TKT[X@878Y3#'#fJ7c@$   VwVv99991<<99990K TK T[X@878Y'.#"#>3232673#"&9! &$}f[&@%9! &$}f[&@Z7IR!7IRb+/10K TKT[X@878Y!!V)9H W@ VV1<0K TX@878YKTKT[KT[X@878Y332673#"&v aWV` v HKKJLDf,@ d10K TX@878Y3# _@ V xV10K TK T[X@878YK TK T[K T[X@878Y4&#"3267#"&54632X@AWWA@Xzssss?XW@AWX@sss#u@  ' 1/90!#"&'532654&'T76xv.W+"J/;<+->i0Y[ 0.W=fB@991<20K TKT[X@878Y3#3#߉fxLu @   '1/90!33267#"&546w-+76 >&Dzs5=X.. W]0i?f7@ u91<90K TKT[X@878Y373xu ?@   : y<<991/900P]3%!!'79Pw^Mo;jnH ^@  z z <<991/90KTX @ 878Y@ @ P ` sz p ]37#'7Ǹ}Lɸ{JZjXjm'6uof'V\m'=uXf']@ <210##    g@    2  y<291/220@(   ]]! )#53!!3 !iP`P5~.,qu('@^%{&%#${##{#({'(#&'('%$%(('"#" ! B('&%"! ## #)&' ! (%#" QE)999999919990KSXY"?*]@v%+("/#/$)%-&-'*(6%F%X X!` `!f"u u!u"%#%$&&&''(6$6%F$E%Z Z!b b!z{     {zzv v!x"**']].#"32654&#"5432''%'3%F2X)6 ~r4*!M!ü޼z&77kc\̑oabk'<su=Vf'\^ =@   ? 2291/0@ ?_]332+#32654&#'ђV>@ GF2210@ `]%#3>32#"&4&#"326s:{{8daa-10!!ת? @M    B   <291<290KSXY" '77w55v8vL57y5yy5 ,@   |]|| 12035733! c)t'+n^J@$}}B ~9190KSX2Y"!!56754&#"56632 "?XhU4zHM98rn81^BQ##{l0b(H@'    #)~&~ )999190#"&'532654&##532654&#"56632 \e9}F4wCmxolV^^ad_(fQI7Z`mR|yOFJLl?<:=svcE`''5 d?''5db''5 dsm'* uqVZH'JP', /uu'6ou{'Vs'k'&-uqf'Fs'm'&-uqf'Fq$J@$ "    GE%<<1/<20`&&&]!5!533##5#"3232654&#"F:||ǧN}}daDDad10!!dHF103#F1@: "+ /) 2+"!)#&  , & &*!/<29999999999122<20K TK T[K T[KT[KT[KT[X222@878Y@z  1Ti lnooooiko o!o"o#n$l%i'i-  !"#$%&'()*+,-2   USjg ]].#"!!!!3267#"#734&5465#7332[f A78 ʝf[Y`(77(6bbiZȻ{.# .{ZiHH"{/ #/{"G)@ dd1<20KTKT[X@878YKTK T[KT[X@878YKTKT[X@878YKTX@878Y@````pppp]3#%3#^ys@B10KSXY"K TX@878YKTX@878Y@ %%6FVjg //]]3#7Ju@!  VV 99991<2990K TX@878YKTX@878Y ]'.#"#4632326=3#"&9 $(}gV$=09" (}gT";9! 2-ev 3)dw @B10KSXY"K TX@878YKTX@878Y@*$$5CUU//]]#ę1w@ 91<90K TX@878YKTX@878YKTX@878Y@ //- ]3#'#Ӌ1@ 91290K TK T[K T[K T[X@878YKTX@878YKTX@878Y@ "  ]373Ӌ ? @   ] <291<290KTKT[KT[KT[K T[K T[X@878YKTKT[X@878Y@T /9IFYi       "5GK S[ e]] !33##5!55bf]my9 j@ VV120K TX@878YKTX@878YKTKT[X@878Y332673#"&v cSRav 6978w{zf103#  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~>: ~1BSax~ & 0 : !""""+"H"e%  0AR^x}  0 9 !""""+"H"`%^ChVjq_8 (Bbcdefghjikmlnoqprsutvwxzy{}|~f55q=3=dd?y}s)3s\\?uLsLsyD{={\{fqqq/q999qqJ+o#7=V;=3XyysLs{{{{{{fqqqqq9999qqqqq9\3 'sLfR#hd+/s`N{H?55=ZyyLss/q%%=V^33 / /9% qyy\\\\;LsLsLs9#LF+o{\3X3 q=55^5bb3sq\+osfqsfqqds 5?+   !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~     sfthyphenperiodcenteredEuroc6459c6460c6461c6462c6463c6466c6467c6468c6469""""XO!nE~Le  R s  X : i  = z /Eu)pP@"m#{CwRw [ r !5!B!!!" ""#"0"="J"W"d"q"~"""""""""## ##'#4#A#N#[#h##$4$%3%S%&&'K''((X()_*%*\**+z+,D,,-P-..R./0A011!1P12H2z23F3p3p3}3334z44445595g55556[667C77888J999)969C9P9]9j9w99999999::{::;;^;;;<"<_<<<<<<=c>;>H>U>>>?a??@:@K@\@m@z@@@@@@@@A@AVAkBEBBC_CCDUDE*E?- x$%&')*K+-r./2934K57D9:;< =IQR&UYZ\bdg9xy&z&{&|&}&9 999 K$$$$$9$&$*$2$4$6$7a$8$9}$:$;$= 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 random_letter_color_challenge(idx, plaintext_captcha): # Generate colorful but balanced RGB values red = random.randint(64, 200) green = random.randint(64, 200) blue = random.randint(64, 200) # Ensure at least one channel is higher to make it colorful channels = [red, green, blue] random.shuffle(channels) channels[0] = random.randint(150, 255) # Format the color as a hex string return f"#{channels[0]:02X}{channels[1]:02X}{channels[2]:02X}" 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]) django-simple-captcha-0.6.2/captcha/jinja2/000077500000000000000000000000001475735403000204415ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/jinja2/captcha/000077500000000000000000000000001475735403000220445ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/jinja2/captcha/widgets/000077500000000000000000000000001475735403000235125ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/jinja2/captcha/widgets/captcha.html000066400000000000000000000002721475735403000260040ustar00rootroot00000000000000{% if audio %}{% endif %}captcha{% if audio %}{% endif %} {% include "django/forms/widgets/multiwidget.html" %} django-simple-captcha-0.6.2/captcha/locale/000077500000000000000000000000001475735403000205235ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/locale/bg/000077500000000000000000000000001475735403000211135ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/locale/bg/LC_MESSAGES/000077500000000000000000000000001475735403000227005ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/locale/bg/LC_MESSAGES/django.mo000066400000000000000000000012151475735403000244760ustar00rootroot00000000000000<\pq_0.-_Invalid CAPTCHAPlay CAPTCHA as audio fileThis field is required.Project-Id-Version: django-simple-captcha 0.4.1 Report-Msgid-Bugs-To: PO-Revision-Date: 2014-02-10 15:00+0200 Last-Translator: Venelin Stoykov Language-Team: bg Language: bg MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1) Сгрешен текстЧуй текста като аудио файлТова поле е задължителноdjango-simple-captcha-0.6.2/captcha/locale/bg/LC_MESSAGES/django.po000066400000000000000000000020441475735403000245020ustar00rootroot00000000000000# 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 , 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 \n" "Language-Team: bg \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 "Това поле е задължително" django-simple-captcha-0.6.2/captcha/locale/cs/000077500000000000000000000000001475735403000211305ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/locale/cs/LC_MESSAGES/000077500000000000000000000000001475735403000227155ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/locale/cs/LC_MESSAGES/django.mo000066400000000000000000000011251475735403000245130ustar00rootroot00000000000000<\pqS#>Invalid CAPTCHAPlay CAPTCHA as audio fileThis field is required.Project-Id-Version: 0.3.5 Report-Msgid-Bugs-To: PO-Revision-Date: 2012-10-09 07:08+0200 Last-Translator: Beda Kosata Language-Team: Czech <> Language: cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2; Neplatná CAPTCHAPřehrát captchu jako audio souborToto pole je povinné.django-simple-captcha-0.6.2/captcha/locale/cs/LC_MESSAGES/django.po000066400000000000000000000016661475735403000245300ustar00rootroot00000000000000# 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 , 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 \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é." django-simple-captcha-0.6.2/captcha/locale/de/000077500000000000000000000000001475735403000211135ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/locale/de/LC_MESSAGES/000077500000000000000000000000001475735403000227005ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/locale/de/LC_MESSAGES/django.mo000066400000000000000000000011721475735403000245000ustar00rootroot00000000000000<\pqs(!<^Invalid CAPTCHAPlay CAPTCHA as audio fileThis field is required.Project-Id-Version: django-simple-captcha Report-Msgid-Bugs-To: PO-Revision-Date: 2013-07-16 12:10+0100 Last-Translator: Patrick Lauber Language-Team: DE Language: de MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1); X-Generator: Poedit 1.5.7 Ungültiges CAPTCHACAPTCHA als Audiodatei abspielen.Dieses Feld wird benötigt.django-simple-captcha-0.6.2/captcha/locale/de/LC_MESSAGES/django.po000066400000000000000000000017521475735403000245070ustar00rootroot00000000000000# 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 , 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 \n" "Language-Team: DE \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." django-simple-captcha-0.6.2/captcha/locale/en/000077500000000000000000000000001475735403000211255ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/locale/en/LC_MESSAGES/000077500000000000000000000000001475735403000227125ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/locale/en/LC_MESSAGES/django.mo000066400000000000000000000011171475735403000245110ustar00rootroot00000000000000<\pqW 7Invalid CAPTCHAPlay CAPTCHA as audio fileThis field is required.Project-Id-Version: django-simple-captcha 0.2.0 Report-Msgid-Bugs-To: PO-Revision-Date: 2010-09-16 12:16+0200 Last-Translator: Marco Bonetti Language-Team: en Language: en MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=n>1; Invalid CAPTCHAPlay CAPTCHA as audio fileThis field is required.django-simple-captcha-0.6.2/captcha/locale/en/LC_MESSAGES/django.po000066400000000000000000000016561475735403000245240ustar00rootroot00000000000000# 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 , 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 \n" "Language-Team: en \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." django-simple-captcha-0.6.2/captcha/locale/es/000077500000000000000000000000001475735403000211325ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/locale/es/LC_MESSAGES/000077500000000000000000000000001475735403000227175ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/locale/es/LC_MESSAGES/django.mo000066400000000000000000000011301475735403000245110ustar00rootroot00000000000000<\pqY!=Invalid CAPTCHAPlay CAPTCHA as audio fileThis field is required.Project-Id-Version: django-simple-captcha Report-Msgid-Bugs-To: PO-Revision-Date: 2014-05-20 21:22+0100 Last-Translator: https://github.com/dragosdobrota Language-Team: es Language: es MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1); X-Generator: Poedit 1.6.5 CAPTCHA no válidoReproducir CAPTCHA de audioEste campo es obligatorio.django-simple-captcha-0.6.2/captcha/locale/es/LC_MESSAGES/django.po000066400000000000000000000017101475735403000245200ustar00rootroot00000000000000# 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 , 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." django-simple-captcha-0.6.2/captcha/locale/fa/000077500000000000000000000000001475735403000211115ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/locale/fa/LC_MESSAGES/000077500000000000000000000000001475735403000226765ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/locale/fa/LC_MESSAGES/django.mo000066400000000000000000000012601475735403000244740ustar00rootroot00000000000000<\pqn##CG$Invalid CAPTCHAPlay CAPTCHA as audio fileThis field is required.Project-Id-Version: django-simple-captcha 0.2.0 Report-Msgid-Bugs-To: PO-Revision-Date: 2020-10-04 19:08+0330 Last-Translator: Mehdi Namaki Language-Team: fa Language: fa MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=n>1; X-Generator: Poedit 2.3 کد امنیتی صحیح نیستپخش کد امنیتی به عنوان یک پرونده صوتیاین فیلد اجباری است.django-simple-captcha-0.6.2/captcha/locale/fa/LC_MESSAGES/django.po000066400000000000000000000020231475735403000244750ustar00rootroot00000000000000# 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 , 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 \n" "Language-Team: fa \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 "این فیلد اجباری است." django-simple-captcha-0.6.2/captcha/locale/fr/000077500000000000000000000000001475735403000211325ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/locale/fr/LC_MESSAGES/000077500000000000000000000000001475735403000227175ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/locale/fr/LC_MESSAGES/django.mo000066400000000000000000000011221475735403000245120ustar00rootroot00000000000000<\pqX 8Invalid CAPTCHAPlay CAPTCHA as audio fileThis field is required.Project-Id-Version: django-simple-captcha 0.2.0 Report-Msgid-Bugs-To: PO-Revision-Date: 2010-09-16 12:16+0200 Last-Translator: Patrick Samson Language-Team: fr Language: fr MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=n>1; CAPTCHA invalideÉcouter la version audioCe champ est obligatoire.django-simple-captcha-0.6.2/captcha/locale/fr/LC_MESSAGES/django.po000066400000000000000000000016611475735403000245250ustar00rootroot00000000000000# 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 , 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 \n" "Language-Team: fr \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." django-simple-captcha-0.6.2/captcha/locale/it/000077500000000000000000000000001475735403000211375ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/locale/it/LC_MESSAGES/000077500000000000000000000000001475735403000227245ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/locale/it/LC_MESSAGES/django.mo000066400000000000000000000011671475735403000245300ustar00rootroot00000000000000<\pqh;ZInvalid CAPTCHAPlay CAPTCHA as audio fileThis field is required.Project-Id-Version: django-simple-captcha 0.3.6 Report-Msgid-Bugs-To: PO-Revision-Date: 2012-11-14 02:53+0000 Last-Translator: Arjuna Del Toso MIME-Version: 1.0 Language-Team: Arjuna Del Toso Language: it Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=n>1; Parola di controllo sbagliataAscolta la parola di controlloQuesto campo è obbligatoriodjango-simple-captcha-0.6.2/captcha/locale/it/LC_MESSAGES/django.po000066400000000000000000000017421475735403000245320ustar00rootroot00000000000000# 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 , 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 \n" "MIME-Version: 1.0\n" "Language-Team: Arjuna Del Toso \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" django-simple-captcha-0.6.2/captcha/locale/ja/000077500000000000000000000000001475735403000211155ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/locale/ja/LC_MESSAGES/000077500000000000000000000000001475735403000227025ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/locale/ja/LC_MESSAGES/django.mo000066400000000000000000000011741475735403000245040ustar00rootroot00000000000000<\pq\"+4`Invalid CAPTCHAPlay CAPTCHA as audio fileThis field is required.Project-Id-Version: django-simple-captcha Report-Msgid-Bugs-To: PO-Revision-Date: 2014-02-13 07:11+0900 Last-Translator: Keisuke URAGO Language-Team: Keisuke URAGO Language: ja MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=1; plural=0; CAPTCHAの値が違っていますCAPTCHAをオーディオで読み上げるこの項目は必須ですdjango-simple-captcha-0.6.2/captcha/locale/ja/LC_MESSAGES/django.po000066400000000000000000000017571475735403000245160ustar00rootroot00000000000000# 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 , 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 \n" "Language-Team: Keisuke URAGO \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 "この項目は必須です" django-simple-captcha-0.6.2/captcha/locale/nl/000077500000000000000000000000001475735403000211345ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/locale/nl/LC_MESSAGES/000077500000000000000000000000001475735403000227215ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/locale/nl/LC_MESSAGES/django.mo000066400000000000000000000011661475735403000245240ustar00rootroot00000000000000<\pqb%!=_Invalid CAPTCHAPlay CAPTCHA as audio fileThis field is required.Project-Id-Version: django-simple-captcha 0.3.6 Report-Msgid-Bugs-To: PO-Revision-Date: 2013-02-15 13:26+0100 Last-Translator: Leon de Rijke MIME-Version: 1.0 Language-Team: Leon de Rijke Language: nl Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1) CAPTCHA ongeldig, probeer het opnieuwSpeel CAPTCHA als audiobestand afDit veld is verplicht.django-simple-captcha-0.6.2/captcha/locale/nl/LC_MESSAGES/django.po000066400000000000000000000020001475735403000245130ustar00rootroot00000000000000# 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 , 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 \n" "MIME-Version: 1.0\n" "Language-Team: Leon de Rijke \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." django-simple-captcha-0.6.2/captcha/locale/pl/000077500000000000000000000000001475735403000211365ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/locale/pl/LC_MESSAGES/000077500000000000000000000000001475735403000227235ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/locale/pl/LC_MESSAGES/django.mo000066400000000000000000000012501475735403000245200ustar00rootroot00000000000000<\pqL'iInvalid CAPTCHAPlay CAPTCHA as audio fileThis field is required.Project-Id-Version: django-simple-captcha 0.3.6 Report-Msgid-Bugs-To: PO-Revision-Date: 2013-08-18 18:52+0200 Last-Translator: Sławomir Zborowski Language-Team: Polisch Language: pl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2) Niepoprawnie wpisana CAPTCHAOdtwórz CAPTCHĘ jako plik dźwiękowyTo pole jest wymagane.django-simple-captcha-0.6.2/captcha/locale/pl/LC_MESSAGES/django.po000066400000000000000000000021231475735403000245230ustar00rootroot00000000000000# 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 , 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 \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." django-simple-captcha-0.6.2/captcha/locale/pt_BR/000077500000000000000000000000001475735403000215315ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/locale/pt_BR/LC_MESSAGES/000077500000000000000000000000001475735403000233165ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/locale/pt_BR/LC_MESSAGES/django.mo000066400000000000000000000011451475735403000251160ustar00rootroot00000000000000<\pqg/IInvalid CAPTCHAPlay CAPTCHA as audio fileThis field is required.Project-Id-Version: django-simple-captcha 0.3.7 Report-Msgid-Bugs-To: PO-Revision-Date: 2013-05-18 13:12-0300 Last-Translator: Alisson Patricio Language-Team: Alisson Patricio Language: pt_br MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n > 1); Resposta inválidaOuça o arquivo de áudioEste campo é obrigatório.django-simple-captcha-0.6.2/captcha/locale/pt_BR/LC_MESSAGES/django.po000066400000000000000000000020011475735403000251110ustar00rootroot00000000000000# 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 , 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 \n" "Language-Team: Alisson Patricio \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." django-simple-captcha-0.6.2/captcha/locale/ru/000077500000000000000000000000001475735403000211515ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/locale/ru/LC_MESSAGES/000077500000000000000000000000001475735403000227365ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/locale/ru/LC_MESSAGES/django.mo000066400000000000000000000013241475735403000245350ustar00rootroot00000000000000<\pqz/DKCInvalid CAPTCHAPlay CAPTCHA as audio fileThis field is required.Project-Id-Version: django-simple-captcha 0.3.7 Report-Msgid-Bugs-To: PO-Revision-Date: 2012-07-25 11:17+0300 Last-Translator: Language-Team: ru Language: ru MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) Неверный ответВоспроизвести CAPTCHA в виде аудио файлаЭто поле обязательно для заполнения.django-simple-captcha-0.6.2/captcha/locale/ru/LC_MESSAGES/django.po000066400000000000000000000020351475735403000245400ustar00rootroot00000000000000# 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 , 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 \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 "Это поле обязательно для заполнения." django-simple-captcha-0.6.2/captcha/locale/sk/000077500000000000000000000000001475735403000211405ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/locale/sk/LC_MESSAGES/000077500000000000000000000000001475735403000227255ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/locale/sk/LC_MESSAGES/django.mo000066400000000000000000000011361475735403000245250ustar00rootroot00000000000000<\pq^!%GInvalid CAPTCHAPlay CAPTCHA as audio fileThis field is required.Project-Id-Version: django-simple-captcha 0.3.7 Report-Msgid-Bugs-To: PO-Revision-Date: 2013-10-15 17:16+0200 Last-Translator: Pavol Otto Language-Team: SK Language: sk MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2; Neplatná CAPTCHAPrehrať captchu ako audio súborToto pole je povinné.django-simple-captcha-0.6.2/captcha/locale/sk/LC_MESSAGES/django.po000066400000000000000000000017421475735403000245330ustar00rootroot00000000000000# 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 , 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 \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é." django-simple-captcha-0.6.2/captcha/locale/sv/000077500000000000000000000000001475735403000211535ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/locale/sv/LC_MESSAGES/000077500000000000000000000000001475735403000227405ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/locale/sv/LC_MESSAGES/django.mo000066400000000000000000000011671475735403000245440ustar00rootroot00000000000000<\pqy.>XInvalid CAPTCHAPlay CAPTCHA as audio fileThis field is required.Project-Id-Version: django-simple-captcha 0.2.0 Report-Msgid-Bugs-To: PO-Revision-Date: 2018-12-03 06:41+0100 Language-Team: en MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1); X-Generator: Poedit 2.2 Last-Translator: Stefan Norman Language: sv Ogiltig CAPTCHASpela CAPTCHA som ljudfilDetta fält är obligatoriskt.django-simple-captcha-0.6.2/captcha/locale/sv/LC_MESSAGES/django.po000066400000000000000000000017371475735403000245520ustar00rootroot00000000000000# 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 , 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 \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 \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." django-simple-captcha-0.6.2/captcha/locale/tr/000077500000000000000000000000001475735403000211505ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/locale/tr/LC_MESSAGES/000077500000000000000000000000001475735403000227355ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/locale/tr/LC_MESSAGES/django.mo000066400000000000000000000011351475735403000245340ustar00rootroot00000000000000<\pqb (IInvalid CAPTCHAPlay CAPTCHA as audio fileThis field is required.Project-Id-Version: django-simple-captcha 0.3.6 Report-Msgid-Bugs-To: PO-Revision-Date: 2013-01-19 20:52+0200 Last-Translator: Gokmen Gorgen Language-Team: TR Gokmen Gorgen Language: tr MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=1; plural=0 Geçersiz değerDeğeri ses dosyası olarak çalBu alan zorunludur.django-simple-captcha-0.6.2/captcha/locale/tr/LC_MESSAGES/django.po000066400000000000000000000017221475735403000245410ustar00rootroot00000000000000# 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 , 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 \n" "Language-Team: TR Gokmen Gorgen \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." django-simple-captcha-0.6.2/captcha/locale/uk/000077500000000000000000000000001475735403000211425ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/locale/uk/LC_MESSAGES/000077500000000000000000000000001475735403000227275ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/locale/uk/LC_MESSAGES/django.mo000066400000000000000000000013131475735403000245240ustar00rootroot00000000000000<\pq*?6j)Invalid CAPTCHAPlay CAPTCHA as audio fileThis field is required.Project-Id-Version: django-simple-captcha 0.3.6 Report-Msgid-Bugs-To: PO-Revision-Date: 2014-02-21 22:05+0200 Last-Translator: @FuriousCoder Language-Team: uk @FuriousCoder Language: uk MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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); Неправильна відповідь.Відтворити CAPTCHA як аудіо файл.Це поле є обов'язковим.django-simple-captcha-0.6.2/captcha/locale/uk/LC_MESSAGES/django.po000066400000000000000000000020721475735403000245320ustar00rootroot00000000000000# 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 , 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 "Це поле є обов'язковим." django-simple-captcha-0.6.2/captcha/locale/zh_CN/000077500000000000000000000000001475735403000215245ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/locale/zh_CN/LC_MESSAGES/000077500000000000000000000000001475735403000233115ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/locale/zh_CN/LC_MESSAGES/django.mo000066400000000000000000000011321475735403000251050ustar00rootroot00000000000000<\pqZ!AInvalid CAPTCHAPlay CAPTCHA as audio fileThis field is required.Project-Id-Version: django-simple-captcha 0.3.6 Report-Msgid-Bugs-To: PO-Revision-Date: 2013-03-01 05:04+0800 Last-Translator: Ming Chen Language-Team: zh_cn Ming Chen Language: zh_cn MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=1; plural=0 认证码错误使用语音方式播放认证码这个字段是必须的django-simple-captcha-0.6.2/captcha/locale/zh_CN/LC_MESSAGES/django.po000066400000000000000000000017541475735403000251220ustar00rootroot00000000000000# 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 , 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 \n" "Language-Team: zh_cn Ming Chen \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 "这个字段是必须的" django-simple-captcha-0.6.2/captcha/locale/zh_Hans/000077500000000000000000000000001475735403000221155ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/locale/zh_Hans/LC_MESSAGES/000077500000000000000000000000001475735403000237025ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/locale/zh_Hans/LC_MESSAGES/django.mo000066400000000000000000000011321475735403000254760ustar00rootroot00000000000000<\pqZ!AInvalid CAPTCHAPlay CAPTCHA as audio fileThis field is required.Project-Id-Version: django-simple-captcha 0.3.6 Report-Msgid-Bugs-To: PO-Revision-Date: 2013-03-01 05:04+0800 Last-Translator: Ming Chen Language-Team: zh_cn Ming Chen Language: zh_cn MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=1; plural=0 认证码错误使用语音方式播放认证码这个字段是必须的django-simple-captcha-0.6.2/captcha/locale/zh_Hans/LC_MESSAGES/django.po000066400000000000000000000017541475735403000255130ustar00rootroot00000000000000# 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 , 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 \n" "Language-Team: zh_cn Ming Chen \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 "这个字段是必须的" django-simple-captcha-0.6.2/captcha/management/000077500000000000000000000000001475735403000214005ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/management/__init__.py000066400000000000000000000000001475735403000234770ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/management/commands/000077500000000000000000000000001475735403000232015ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/management/commands/__init__.py000066400000000000000000000000001475735403000253000ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/management/commands/captcha_clean.py000066400000000000000000000015761475735403000263310ustar00rootroot00000000000000import 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.") django-simple-captcha-0.6.2/captcha/management/commands/captcha_create_pool.py000066400000000000000000000021001475735403000275230ustar00rootroot00000000000000from 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" ) django-simple-captcha-0.6.2/captcha/migrations/000077500000000000000000000000001475735403000214405ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/migrations/0001_initial.py000066400000000000000000000015741475735403000241120ustar00rootroot00000000000000from __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,), ) ] django-simple-captcha-0.6.2/captcha/migrations/0002_alter_captchastore_id.py000066400000000000000000000006151475735403000270000ustar00rootroot00000000000000# 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), ), ] django-simple-captcha-0.6.2/captcha/migrations/__init__.py000066400000000000000000000000001475735403000235370ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/models.py000066400000000000000000000053571475735403000211330ustar00rootroot00000000000000import 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().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 django-simple-captcha-0.6.2/captcha/serializers.py000066400000000000000000000021461475735403000221750ustar00rootroot00000000000000from rest_framework import serializers from rest_framework.fields import empty from captcha.validators import captcha_validate class CaptchaSerializer(serializers.Serializer): """Serializer captcha code and captcha hashkey""" captcha_code = serializers.CharField(max_length=32, write_only=True, required=True) captcha_hashkey = serializers.CharField( max_length=40, write_only=True, required=True ) def run_validation(self, data=empty): values = super().run_validation(data=data) captcha_validate(values["captcha_hashkey"], values["captcha_code"]) return values class CaptchaModelSerializer(serializers.ModelSerializer): """Model serializer captcha code and captcha hashkey""" captcha_code = serializers.CharField(max_length=32, write_only=True, required=True) captcha_hashkey = serializers.CharField( max_length=40, write_only=True, required=True ) def run_validation(self, data=empty): values = super().run_validation(data=data) captcha_validate(values["captcha_hashkey"], values["captcha_code"]) return values django-simple-captcha-0.6.2/captcha/templates/000077500000000000000000000000001475735403000212625ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/templates/captcha/000077500000000000000000000000001475735403000226655ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/templates/captcha/widgets/000077500000000000000000000000001475735403000243335ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/templates/captcha/widgets/captcha.html000066400000000000000000000005711475735403000266270ustar00rootroot00000000000000{% load i18n %} {% spaceless %} {% if audio %} {% endif %} captcha {% if audio %}{% endif %} {% endspaceless %} {% spaceless %}{% for widget in widget.subwidgets %}{% include widget.template_name %}{% endfor %}{% endspaceless %} django-simple-captcha-0.6.2/captcha/tests/000077500000000000000000000000001475735403000204265ustar00rootroot00000000000000django-simple-captcha-0.6.2/captcha/tests/__init__.py000066400000000000000000000000721475735403000225360ustar00rootroot00000000000000from .tests import CaptchaCase, trivial_challenge # NOQA django-simple-captcha-0.6.2/captcha/tests/drf_views.py000066400000000000000000000016751475735403000230010ustar00rootroot00000000000000from rest_framework import serializers, status from rest_framework.decorators import api_view from rest_framework.response import Response from django.contrib.auth.models import User from captcha.serializers import CaptchaModelSerializer, CaptchaSerializer @api_view(["POST"]) def test_serializer(request): serializer = CaptchaSerializer(data=request.POST) serializer.is_valid(raise_exception=True) return Response(status=status.HTTP_200_OK) @api_view(["POST"]) def test_model_serializer(request): class UserCaptchaModelSerializer(CaptchaModelSerializer): subject = serializers.CharField(max_length=100) sender = serializers.EmailField() class Meta: model = User fields = ("subject", "sender", "captcha_code", "captcha_hashkey") serializer = UserCaptchaModelSerializer(data=request.POST) serializer.is_valid(raise_exception=True) return Response(status=status.HTTP_200_OK) django-simple-captcha-0.6.2/captcha/tests/tests.py000066400000000000000000000610041475735403000221430ustar00rootroot00000000000000import datetime import json import os import re from io import BytesIO from unittest import skipIf from unittest.mock import call, patch from PIL import Image from testfixtures import LogCapture import django from django.conf import settings as django_settings 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.models import CaptchaStore @override_settings(ROOT_URLCONF="captcha.tests.urls") class CaptchaCase(TestCase): def setUp(self): self.stores = {} 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_WORDS_DICTIONARY = self.__current_settings_dictionary settings.CAPTCHA_PUNCTUATION = self.__current_settings_punctuation def _assertFormError(self, response, form_name, *args, **kwargs): self.assertFormError(response.context.get(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() @patch("captcha.tests.tests.random_color_challenge") def test_custom_letters_color(self, color_challenge_func_mock): color_challenge_func_mock.return_value = "#ffffff" _current_captcha_challenge_func = settings.CAPTCHA_CHALLENGE_FUNCT settings.CAPTCHA_LETTER_COLOR_FUNCT = ( "captcha.tests.tests.random_color_challenge" ) settings.CAPTCHA_CHALLENGE_FUNCT = "captcha.tests.tests.random_char_challenge" challenge, response = settings.get_challenge()() _store, _ = CaptchaStore.objects.get_or_create( challenge=challenge, response=response ) _response = self.client.get( reverse("captcha-image", kwargs=dict(key=_store.hashkey)) ) assert _response.status_code == 200 _calls = [] for index, char in enumerate(challenge): _calls.append(call(index, challenge)) color_challenge_func_mock.assert_has_calls(_calls, any_order=True) settings.CAPTCHA_LETTER_COLOR_FUNCT = None settings.CAPTCHA_CHALLENGE_FUNCT = _current_captcha_challenge_func 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_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_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_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 = ( '' ) if django.VERSION >= (5, 0): captcha_input = ( '" ) 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. if django.VERSION >= (5, 0): self.assertInHTML( ( f'" ), str(r.content), ) else: self.assertInHTML( ( f'' ), 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( '' in str(r.content) ) self.assertTrue('id="form1_id_captcha1_1"' in str(r.content)) self.assertTrue( '' 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_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 @skipIf( "rest_framework" not in django_settings.INSTALLED_APPS, "Only run if DRF is installed", ) def test_serializer(self): r = self.client.post( reverse("captcha-test-serializer"), dict( captcha_code=self.default_store.response, captcha_hashkey=self.default_store.hashkey, ), ) self.assertEqual(r.status_code, 200) @skipIf( "rest_framework" not in django_settings.INSTALLED_APPS, "Only run if DRF is installed", ) def test_wrong_serializer(self): r = self.client.post( reverse("captcha-test-serializer"), dict( captcha_code="xxx", captcha_hashkey="wrong hash", ), ) self.assertEqual(r.status_code, 400) self.assertEqual(json.loads(r.content), {"error": "Invalid CAPTCHA"}) @skipIf( "rest_framework" not in django_settings.INSTALLED_APPS, "Only run if DRF is installed", ) def test_model_serializer(self): r = self.client.post( reverse("captcha-test-model-serializer"), dict( captcha_code=self.default_store.response, captcha_hashkey=self.default_store.hashkey, subject="xxx", sender="asasd@asdasd.com", ), ) self.assertEqual(r.status_code, 200) @skipIf( "rest_framework" not in django_settings.INSTALLED_APPS, "Only run if DRF is installed", ) def test_wrong_model_serializer(self): r = self.client.post( reverse("captcha-test-model-serializer"), dict( captcha_code="xxx", captcha_hashkey="wrong hash", subject="xxx", sender="asasd@asdasd.com", ), ) self.assertEqual(r.status_code, 400) self.assertEqual(json.loads(r.content), {"error": "Invalid CAPTCHA"}) def trivial_challenge(): return "trivial", "trivial" def random_color_challenge(index, string): return "#ffffff" def random_char_challenge(): chars = "abcdefghijklmnopqrstuvwxyz" ret = chars[: settings.CAPTCHA_LENGTH] return ret.upper(), ret django-simple-captcha-0.6.2/captcha/tests/urls.py000066400000000000000000000022521475735403000217660ustar00rootroot00000000000000from django.conf import settings 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, ) 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"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")), ] if "rest_framework" in settings.INSTALLED_APPS: from .drf_views import test_model_serializer, test_serializer urlpatterns += [ re_path(r"test-serializer/$", test_serializer, name="captcha-test-serializer"), re_path( r"test-model-serializer/$", test_model_serializer, name="captcha-test-model-serializer", ), ] django-simple-captcha-0.6.2/captcha/tests/views.py000066400000000000000000000057341475735403000221460ustar00rootroot00000000000000from 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""" captcha test {% if passed %}

Form validated

{% endif %} {% if form.errors %} {{form.errors}} {% endif %}
{{form.as_p}}

""" 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_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) django-simple-captcha-0.6.2/captcha/urls.py000066400000000000000000000010141475735403000206170ustar00rootroot00000000000000from django.urls import re_path from captcha import views urlpatterns = [ re_path( r"image/(?P\w+)/$", views.captcha_image, name="captcha-image", kwargs={"scale": 1}, ), re_path( r"image/(?P\w+)@2/$", views.captcha_image, name="captcha-image-2x", kwargs={"scale": 2}, ), re_path(r"audio/(?P\w+).wav$", views.captcha_audio, name="captcha-audio"), re_path(r"refresh/$", views.captcha_refresh, name="captcha-refresh"), ] django-simple-captcha-0.6.2/captcha/validators.py000066400000000000000000000016011475735403000220040ustar00rootroot00000000000000from rest_framework import exceptions from django.utils import timezone from django.utils.translation import gettext_lazy as gettext_lazy from captcha.conf import settings from captcha.models import CaptchaStore def captcha_validate(hashkey, response): hashkey, response = hashkey.lower(), response.lower() if not settings.CAPTCHA_GET_FROM_POOL: CaptchaStore.remove_expired() if settings.CAPTCHA_TEST_MODE and response.lower() == "passed": try: CaptchaStore.objects.get(hashkey=hashkey).delete() except CaptchaStore.DoesNotExist: pass else: try: CaptchaStore.objects.get( response=response, hashkey=hashkey, expiration__gt=timezone.now() ).delete() except CaptchaStore.DoesNotExist: raise exceptions.ValidationError({"error": gettext_lazy("Invalid CAPTCHA")}) django-simple-captcha-0.6.2/captcha/views.py000066400000000000000000000203331475735403000207740ustar00rootroot00000000000000import json import os import random import secrets 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 index, char in enumerate(charlist): fgimage = Image.new( "RGB", size, settings.get_letter_color(index, "".join(charlist)) ) 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(), f"{key}_{secrets.token_urlsafe(6)}.wav") ) subprocess.run([settings.CAPTCHA_FLITE_PATH, "-t", text, "-o", path]) # Add arbitrary noise if sox is installed if settings.CAPTCHA_SOX_PATH: try: sample_rate = ( subprocess.run( [settings.CAPTCHA_SOX_PATH, "--i", "-r", path], capture_output=True, ) .stdout.decode() .strip() ) except Exception: sample_rate = "8000" arbnoisepath = str( os.path.join( tempfile.gettempdir(), f"{key}_{secrets.token_urlsafe(6)}_noise.wav", ) ) subprocess.run( [ settings.CAPTCHA_SOX_PATH, "-r", sample_rate, "-n", arbnoisepath, "synth", "2", "brownnoise", "gain", "-15", ] ) mergedpath = str( os.path.join( tempfile.gettempdir(), f"{key}_{secrets.token_urlsafe(6)}_merged.wav", ) ) subprocess.run( [ 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): # Move the response file to a filelike that will be deleted on close temporary_file = tempfile.TemporaryFile() with open(path, "rb") as original_file: temporary_file.write(original_file.read()) temporary_file.seek(0) os.remove(path) response = RangedFileResponse( request, temporary_file, 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") django-simple-captcha-0.6.2/docs/000077500000000000000000000000001475735403000166115ustar00rootroot00000000000000django-simple-captcha-0.6.2/docs/Makefile000066400000000000000000000061201475735403000202500ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/DjangoSimpleCaptcha.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/DjangoSimpleCaptcha.qhc" latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ "run these through (pdf)latex." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." django-simple-captcha-0.6.2/docs/_static/000077500000000000000000000000001475735403000202375ustar00rootroot00000000000000django-simple-captcha-0.6.2/docs/_static/captcha3.png000066400000000000000000000226411475735403000224400ustar00rootroot00000000000000PNG  IHDR(}9DpiCCPICC Profilex{4T{fZ0'aEm:]\rLZ鶲r wg+yVV^x~R+9{kbj_Zǯߠ(wfíwߍGoѢz-C#ܣ>^Y/x/C_9wwfuvw^O}&;w ?d~L3W~~\W`^}p_9qWKt(Gp7%poq8O+$t$\$%r%Hde6 % {De"A|(%Nꊴ4S~1qc^˸ȔN"B^._/OU0TQx`l%#ZTSwDU_r'lF@S3>D<}4(OkG5-鈯%b$Ct qc4sωCj#)!Zux4! D=-Quu;S42Ȼ{4Mf56o)yl718y-\`,rҴ S|Y;?ݮYp:hLXx;S*o``uű/Vq”B /c8G^vŌ!Ѩ.8i̘f vBCĻzcpRUkXĄYo^<,d1iq#S+4ݺCz/ZV=C]s\Dx!>@L@if{kj:ձ̅?Yw(%zկDyB#`U_.;[V{IeWuj/On![?]8019f9ht%,\Pн?gwWNIĎ6?_'+ӫDYB# )@[K}]/jZ"Xw+G6瞳xf[hk[$2oZX: u!@"=P6;=DN?{ @ijfG`ċ݋O{@mF@Yhk˨=B_ZEq-YP:.D{m2Fq ukK90D<8Ե^[́ 9šu\xڒeρ/u"@ז,s`x|qkQ^*---WiZ~u 9ss%J!kA:|YMtt鸍9O2G&ѫڈ$U?bO͂-#!iT%Ϝ<׺~h,̵u*/08~* dBb׌;4"!49͡5o-s矙4;,9Ō%MԄM湫 uSwww>(Tq&/JŸGWy,U`|gK.0}&$1vϿ:D@5"cXg`hEzz'#|9u$3K&cZ4ka5seThҗ̊^:oָ⬍;Q,yキxr|aGtĄ(beL/8*vUTyO*U+i j/]%=gpH!^XؗIYw[gr9SBsՃyʵ u 1U3Օl }7䄹S"L5Lo,.`Ǖ9WOO?4v !J$(vaDi#Q]cXܿx 'w7Ud A@IBo~(n\Ry߼ҫ)7JʊFt\-:F3ԝ8S2ـ)L5JU8jy}ZUiBʹmogr:)0kcd-XV,$cɑUQp[<{}L2=>$be7r5'F Mힱlጩ)?p+Z6k=RG颚QCw2yOV-% 1Io;ÆbIpDy1`VU^0w4Z7ZWUJ-䯟EFh5QS(Ʉ"NVt,cѨʙkNRU6BŔwQHnXρ~0^SdJ lpA2"8'M&¡D􇹍+uţ#6›qD@<&p&xqO1$ 8=YK˯v.33q,vG=#q<ޮ]Һ:5zN9BCOkL2+P'++9=phAct`W̍6tHV_|IQyzzZ2AU_}'mɣǏAM&㓫SKZBFykP/nybuO ͑$Y\w7di+~dN4#XR,kF폀ØonOg-Y;5O,a:r2fFp\&)hMă@uuum]sTwb(SUdInjO@Sy;ᦺtk 2TV\ŲP=msgΖu é+eW zS`g^_ZTߘkmī6~̰1Kg O 'ֽi$W1>-k: ax"fp`pltw;yBo/o>R?VUUUtל_l $hRbn= Tākӳr@n \HWW5u `d*Bgx)qwμH ]?] {_ 7=I5tL8ikfp?}a}1rbFI<pN^B&!`#j)ۯ::>Up2pki..`&\bB~9Sz ֍(&XOb)MW1G mgȀUU 3&~52#탿6|&c~ń'b\j_IhE®"yfIi/'~ρ'xv-۶_-[+(=|T|T"]v蜜9^10VP7\_XhՂu2n/ooSfbUu 5S._9Q:ͷ8+:w80ȶa7|IkSЅxO*8SO  T|i7QO>ݻvo"# 6sS+=Sx؉c`,@0~KA`AzAO6ok#}vƯHO m)ܴ|r~VMa" VoNLlqCdxB1O-Daa}S1ܾk{3PJN//@X:|R0Q\a"[  񕱷EdE."G<:7,+z{R[ IliM-Agf 1E[gm-zĘ˧$ߐ0~-{6<¨B1jtle':Mػ/B#o)R **Z:'ā e!x'V%'%B'Rх"xѷ癍*|Vd>X~i?6EJ.wfNRpTafj~*1V!lmÚ ӼG>e┞=4PiF|GuGAt[لZ5_jq!C#0)P^I&MgkfzcEM/wn`*pkb$nn 7>oiE*1-\ X) [sR腕Y#4>1.61AK_w=5*mߨpЁ(ʡR#%i[U0][$\!^F5*4=P̴"w?~ n9x^3~tQš<nR 4Cߠlli('8um/o((/t A_PV`Ayxjauadx`O:'BE*ă<a}A< r"nӈ3F!X@bȹJʡ !VBBqhvFז.bipؑþm@ *C P~b)-;G9_x@RWFs:KbH2@*~/xuY@ˡ` YB?_|-` !rCM b˒Y'+"摒ybpwWZ![R}χ(-XKsE@|<`] @ZLI?p"SxP !`uxV L bB%V&5N۰>%NEooo|J}#Y NgϞϝ;bԚ>]x^ѵk!Cs"vFNA#FqݺuC78?ĉk׮%EEE{]iO\<;_?7~\Dψ%77{ׯ]s<`1pbʕ&M[]ej3L'g'VϜ^3\__[Wصk0@,7NkCQܦMqkc+Z˸O]R_a OOv1ڰCa250mٲ Ki;A;-<!OF$,~A~^wgNCK=<^?Z)_Wsx#RZP"nD";yf\\b!xx  (gM|#vMpS+/yiʜs;3JeYI~LeUڇh1gµ2xVܩkfЃqJT"@uUU]}ub\xx!c SX!Spl7caf_S9soZUǚ?VqS}KO|lNm>_.5yE]f${W֨u-gԨjj_?Aɾ?n0= 66aᖛ{Fɪ6?XqQ":[Ka/Ʃ{<"^#yzz"oĻ~DSub}9Ffn fE2jHJ6}{obLݻ/pH_pP7$ TR"qug9 만 nn;xaGL7Xq)UJ$zDV8(̟ilS*b +!,mRE;HxјXPπ,'uҒZNVoZпܟ`) 4{?{MJ\p$WʼnIe _(=Iz*'Mf Hbਗ਼CB}y=-|FL\i0 X V}ڎagԀ!fh~ M1GQo~Qpȑ F=.O ww8 **[|c;[.hœߘGb͊ _|b=*;ю9=y.]̲glDXjKNΨ3.Pȑ#LJJ {p@D+UZ&qWzbbِjZ+zcA3͒wN#3QUT֪{`)$m=5U(u{\msgQNkS"n>p@QQ?ԯKlNMI(Fy.\:Sm%DJdD<$U ]/lsL B6l3B @3 @ij !`: l68S+D<8 Dt:R2wgUWt_x~R+ٕ{bZ\S\#nPR/]sfݭ wݍGo٤|%C#чOnzzY}m//^9+gvuv_Oz&;w >d|٫kf|/J_㿹|w="o;?' DTx8^,Н UpSӡKR?|!Tx0EL3bۂ B{}eqM|sTot\brHte.HD&{$Nz(-Z抬,KvĔGs;#?^>NB"AXd=rGe)+W,WwjjccގM7kK}5p zOJ"[4'OܬRBWAٔҩVh PN˝mHӘdo8dYjsT玞nIҝohmnco n}CņINM.Y437}w+OW+5KW#J"&-8D'`SG[D>{- g.]?zōZm}ᎊ I%)λTK8iuV}#+&[.'+ci'';)*yAIzɭKkuQSnKÚϨmv/i:ޚ [mُ1K%t;)"˳](*nBI8Q[g qi^I_L0&a6fH !ͪ!M sXaNM(oGO%@`@ [~_cA%m;#[I!/b1S&FFoT* pHYs  IDATx XSWO  @B*2NkvZkZ?N #ZTZ$` .!{ {{qo0ڈe^l" Q;  @Jse*Ҩ w 2AHi;Ȼ(t{Ҭu4$@DtM\Fb&C'@J)Y$b41! Iϔ,1R  ҤgJ)M̄bHi3%D@L&fB1D@z4陒E" &@J3" =RL" P )Mzd L(HO&=SHHib&C'@J)Y$b41!Ti&FMfkj:{&͒="&t-ǷyfeoZa|\iDOH4Wko/Ufi~n\x]銯֭:U΍s2q/=b+:=Ug~aQUۑчdp)BZ(2P׽[H\jYk[ƆfEYW@ڈ`XĬ 2\~P38h6VU~ݣWӹwhqwg`b)Q mUs ֽ鵕+ù~'m^0< ]"Rwa׶lِRVaU T!}M@>bm9#I&GC uk2NB3iCDZjjbX f>~f"NꡭYL1OѰmD*~#;f}x0c?UPLzI|>@ibm,7jon^)S*  j}X@k6L_uPՔz6y6nm+OZ=r#"@!{Y)}7f~,8#{Գx>1[:r;Ǚ mصkڬbCֆi &WO2UYɄj_m|v]Xy+;vT{Jk6W=z6o0}Fof 2}Ɲ4z}eR[8}y{ft_nfmZ.\~E&mDc#H1g{u:N•&Ia7?)SN?X:p)SV? ySK_nOaXֵGnm a#>ܾfv>w2/߅doszۇ{NMD6")d---ꂷ:^15ə2XhI5+UJzJi*ݵt>o֚Je{:X:11lwۮIe 8^ovb%n w͸V'%W"1 Uۜ z5S !u UYF) 2a>g" ? mDS X6mwQ9 fG( (:T^^jJٳgwqy 7/^ :޽q/L@apiBb(MRY3 %vg'/@ *^2NҨ)w 2A$^U]p[[!B-p"FY!W -F^,{A8$- #L PT`K'@,-9pR+MS^jSZk\TV?ƫ@2o/]0l -հYU5P+wGP4]{âG>>}a׫{`Y4=JCcN>R\.o"P&z  0%.h8*|yb4 +_7~fO38ϯMl,˥Xi A*,.D_F{o8y[GM1ⰫоВZVuXvQH|@𨹹`\q9+9MMM .ؼ:BR`viYWWw0oL☠ .GcB{HcF ~~kiuLu(cbF kLFN;-cڧ}zء_MJLT6P(6<@WF3kڬ% ޮVPI?|>hcKjjk0CDIMJl3Cy?Ub@VP+N4Edj{ڒp ó!q~RQQQQj/+ Ya" Yew.j R &߬i0p-61r`BMJXΞ9mfg=vWr~J|檺@T܊`0Xl47ZVC G`i}NMG7+04fhc2xBѸ Ykm88Rc KCb̙9gmXp> 'r fbq:54?t]! v]$ޮ Sab2[Ƈ铏oXyhOuI9_6LP H&@ v߾}ʏW̍ȈH{S) 9yW nϝ5oKhA ݛƵku:<Ӳc}*A$ ; 3Ok ꜄V R rHrR2." c'873DD⿕;Lf/c gM1 G-MY&"6B1E= pR{8S))m=HiL t 32 z CT@' ͕k t}%P@v9J"%H&C4()Md8$@Js $H&C4()Md8$@Js $H&C4()Md8$ -CD` p({ $xޣ=O&qҬ|OHD L(HO&=SHHib&C' oYj:/m[Gڪ^E9{x9h%quvɓ'_¡Z=jlB8AxUPLLLFFZ{u-/J-&PVV_Bc3f̘?!C4.syfLss3W;w.'QbbbkKzp@[8;޿0~xĂ?bA=/N42 4>qn22~[Ouڛ+gU_|nlE0xd-[CC|<~@T*ۥOs %n#dNZ2FDXuf\l77{$5FX_oМBKkS_Knٷ6VYʌA!W^e PoyrT!r +r/eqFOWCsK3fbnԧu͈>  ",, 9 8B1Dx87xbxǏeL\t?5h9 |z_3nPy`F:"ƪ|X~"Ň~w)/I0_?\g>Q|\Y þ7~58f{O/ơivB=67A|2IMsс?'bZ1գ1Onֱ9[Oc̘rPBJ- yys)roh(cr?kXƑ7+=;WĬUyMuշ%+}\hr'68!1G0]#7L.a @+&+4 46m*Ok~3+ԼһK6&:5 my!C)͔Lϧ2p1,'5.X&2U)32Ѿ-1QC:G4gIQ:#ČS@ O+\V&.eرMA59e&ę2[dLry:/5NG EJ.{qET/kTu?o>? Py | ^ \ \E9hs`ЅE-Z6bn?cpbF\7fCo|b8--=juK.? =]dDŽVJ5#6TO:>բ/^/ E=% lS#B/7uf>Qӧ񙞞!L u5U3bض:YkvbS/gxsȠYXKjqa̙3-٠ ;;枩(ӬЮchjN'NTVVnw[GC"!a]7>m'Ĺrą2㯪_k2[cHiP|W 脄/}\Z]e'ǰ+**JJJFc=~nI8cԨQft' BCCC)vHi*ďqIpZ74GllRt8;88lp*&Jdѣe .jH [ON6rO$ eak\`o-Mqhh5n4LzCp9K(n8.uHi]N@sDDφ#9#K;uVC:$CtDEf  Ҥ&J-4[T(HM&5QGl ٢BqD@j4="`)#R IM[HiPp%="0 tȃ|&CFÕHiyJV@gt:R2wgUWt_x~R+ٕ{bZ\S\#nPR/]sfݭ wݍGo٤|%C#чOnzzY}m//^9+gvuv_Oz&;w >d|٫kf|/J_㿹|w="o;?' DTx8^,Н UpSӡKR?|!Tx0EL3bۂ B{}eqM|sTot\brHte.HD&{$Nz(-Z抬,KvĔGs;#?^>NB"AXd=rGe)+W,WwjjccގM7kK}5p zOJ"[4'OܬRBWAٔҩVh PN˝mHӘdo8dYjsT玞nIҝohmnco n}CņINM.Y437}w+OW+5KW#J"&-8D'`SG[D>{- g.]?zōZm}ᎊ I%)λTK8iuV}#+&[.'+ci'';)*yAIzɭKkuQSnKÚϨmv/i:ޚ [mُ1K%t;)"˳](*nBI8Q[g qi^I_L0&a6fH !ͪ!M sXaNM(oGO%@`@ [~_cA%m;#[I!/b1S&FFoT* pHYs  zIDATx \TeǟaEV}Lm͖ʒf^-Ӻ]OakWOַ4\_2[2R#$EeS``w80039ӌ~syy~fFxFt"@x$9DHr.M7#$9DHr.M7#$9DHr.M7#v"`@rr.$kbcc^ID8"PZZ5,t" $X(pD$ I$JF#$9Gd(HB$' V2J 9"CD@$9IQ"IJ' IGHrP:INd8"@sD҉$Hr`%D#2N$!@+%t" $X(pD$ I-9#ju$vygͽn,#.% u8co^3[}=S{uLP8i.' $\`k:5vUyXrdkq()5_,ݚ'#Q7Tcٗ[EoN35QIQW}//}<^̇qfΜo`l\g'.يly>{Nƺe'!gl|kqoƛy\xeKR빻ValPY3^~ F܌8}9cƶC ^+'Moxo̕r h[lH`1oye0>+漴ފYtHjc+֭JO,|7UrC”V"ĉr**dLe#Gʌ=Mjͦ^H_=ˏ''%Ňf^`#~؝<;qԠKz>Gp9>*IػA45@%A1]z%nF@E Ȧog3qlv~m=6kM71=qN 0Pn< _V|DCܖ8 Ku0YA?ogU/~]7 gU7O|̃$f XUӌ[yvڵ"}đ<'YjFLΕ\!AD"vŔsBg2p7?bܥZcSg2.kZ+/>Slo:Ig2г c+6i#nFs\Y^'3L߻xػʵ-`ęe}vv.s|eIse]peO+Vl]}:dlhڶP_o\{[͜zOur{DOI@g6"^d---ѓwLNkl3ouF&gNgRk8tB6ĚtFZݞ/YLaNB47阂,.R ;U'Wk": z@ ~k:&,e}>Q59ih#M_jz$E6E@eggԭ^ӧOw^}8[G}dK'OĴ{ˬDܭ QW3Jyߏr>N$+j$9Wq$>^}WY皚~ R)ɜ񲲪R[YX#j.4$4,$,,4t-Ȓknn\|ȱ#?kAP4:i1c4A^^V+9K]J뀅ݍȒ/sM&_Ψ^y|a~A G ل+7͎J"nL@di?CjQ#GIq\~Q?##"J5W zCD63[ai>"Ė\6 ˹MMjDxoCc#6luMA"Nv:I&uHC"Dc#"r974NR SST` ?]`L`HpH``kZloKC/L(E""K@h  U ]^r>DGEwrڰu4#8`ۧLHXj6hŜCC/f#5%p1k,4 g544@u|_+*.&:`8/}ďrIzX;uԓN])Q{l6OuرN!ȒCjjK ˄ ܺsG~H^r ?'pGsn**$(zSl(joSȒGlA3/xL XN UP|,β]%'{4UU` Ɔ  ,9L `˾2G9LXI9~ U7_EwQQ(!t/1|b6:H'PD1^F-,¢,jĄDL[ 4\wىuC Cr!@ccF^ߚ.$Yr].Eƾ 3 RbI ZB.:jij 1ώD<1,\B,9O=ȆٳظQ~= _AG^ =:lVij/,nni 2ZWQ p"K1 C翢U *b:ZR<DDDCDN5sFtH$" PJ(ʯuÊǞD8eO܄@￝&P1g yEx$9 "@JHrE$Y <-$<Iγ/*m*c=4kt{H~/=!@ K$@$@.&6Hr6H(HI$'%]MllPINJd " %t6!@AB D@J$9)m"`C$gđ\ii) ID/pR2d'&v‡Nѓl8QήiJ$DIΖ  $K--J![L{Nk655u>2nx0ᄧ ۾V+W|7W^t%w 7n &bIr"컦JJJ>mԩf8p $'y"<';v|п?x//4/'ɾkٳ۶mKHH{cJ G}tԨQwy9rH9;@@bM6͝;wƌ _KZ z9FJ Ehh(^@)=}WG9fW+|afN祌_p3K{5ԩ׏;tSotڶ7wc-;9hju ;5V Ɗcf+;ziFG _؝09R1$tЊ.i ~78q f&xO/)gI:!5 ' `BP*7/^VfM>bف_hgWx赦IW|Gi#[^disGȽ!£uB9y"'yayNfrq3S8IQ*N'i<7Px 蠚v䜡wEC1*emU]6L^P䀡wk;_(/|?B *XF;݀'4RN^NQwG§FƬ!/|H'4,gw?1p5|w捧_ע}"@AD@j]/p A@!o)p jX3݅ QU .%@s)n Q .%@s)n Q .%CN/IENDB`django-simple-captcha-0.6.2/docs/_static/random_chars.png000066400000000000000000000177101475735403000234130ustar00rootroot00000000000000PNG  IHDR0ˮs[oiCCPICC Profilex{4T{9 yRI5(Ly"QifIwHN#)BB/ \&w/{~s6R6 l^Ȥf e[SNH 빏c_c`R&km+$[(ȡ<0byH"fF=q,b ǝf(bA.s ׈9, @TfHo[2 X 8q}xxx*Ask f~|EEc[t Jh?vdj" Hԥ п[$H=ƈD"5vR(9D`Ȁ&l%B0&ȼDਯs֌MT_a5c&Ӟ0 ) ǚdP60{EO-IV]cmN~mfecS’BLjkmZwG=2i̧'~(3N1:BB)+ Wi{X-q B R%_SS[q6l۲mv,wi`LLՙҴ$5zE)Izފ}W?e(geVPK8pYeGB86X L ΢uV**>t:R2wgUWt_x~R+ٕ{bZ\S\#nPR/]sfݭ wݍGo٤|%C#чOnzzY}m//^9+gvuv_Oz&;w >d|٫kf|/J_㿹|w="o;?' DTx8^,Н UpSӡKR?|!Tx0EL3bۂ B{}eqM|sTot\brHte.HD&{$Nz(-Z抬,KvĔGs;#?^>NB"AXd=rGe)+W,WwjjccގM7kK}5p zOJ"[4'OܬRBWAٔҩVh PN˝mHӘdo8dYjsT玞nIҝohmnco n}CņINM.Y437}w+OW+5KW#J"&-8D'`SG[D>{- g.]?zōZm}ᎊ I%)λTK8iuV}#+&[.'+ci'';)*yAIzɭKkuQSnKÚϨmv/i:ޚ [mُ1K%t;)"˳](*nBI8Q[g qi^I_L0&a6fH !ͪ!M sXaNM(oGO%@`@ [~_cA%m;#[I!/b1S&FFoT* pHYs  IDATx XTǿaW b,ŲrviLtt?N;zxzZ2+V3,/03X00 :katؕ Ү0"` 2:KJiWTLi%v%@+n*X&@̇ A7F, AZCg] 튛 # -DHvMHY"`W$Hˆe$H|,+UɯrJAړ{w_:[ʏ8@'~*w?&+{T{ӟ^\865LrQr"\^_"zkHUݨ,+"T1/`ƕ+8\] ;Շ')8T[~p} ˘|jj.OǞ6oӊUrnS0'3myOffnx{ciY _-1=zKV? +?rU\~YK&0Oo߱䃜SФ̅ [/a,&kNMbLgɜ#o4g `>q)j~[K]'*pR:LJ[) snȗbe%y6wp`U9!YK>Xμ!fW-)U>@F^,+<} uGEta6W W6駡Aebc"}f8+g?y C%LoΫS9G4',t[kB2}{'> O׫BWA2]z'@Ƃrv.a2SOC޾-=E7n35! SUd2 T_Fdo"oU\HsqUOS`BAylܚl(q0tSn{^Px0m청[.ΊiitL$ R9$V}r߯p榬b[z*MBj=44!eerIRTWlʓT%'KM?r2U9Q*}= \g `h:ڪ>aZxNK SťLLbKW6ɒ&eת #>ټfob&e-cތjK&-<\~KK2$;!y;jق==,ѴеZ&ev~/k&_vQFɚjqtz^k]/iQVe8R)TdE?/ٸ2^Њex\}0eC|^/}lZI*Qb)؁~%:U{?d(Zo/BMS2V3D6qZ45ۭDϟ`6"z{W_eƇv l,}uҒ&Cۡ* Mpm۶׹5N:bp,l "~Nam+1?f貹xe9pHd8 Aڇ3B"@ E"!@g*XE@AFF7eRY``KfВDQN- `8Ijy5l 1k4v %gh6 $! uAiY鑼#gTVUj5ZD?qԈQqAXhٴZORTRTyRxzz 7p䰑}@x^C+>\ ^]Ю =w/#"080x@€ c&$I1ZOF@@Atk~:m]En"7N{>5)h,"<굫jW%kbbK_tryiN?z|td4 fMpqCx?jVל?3g̙='%)%?$J5Ǐ~ ګb{+:u݅ ,XpMG6&Cn\m~2b{|zEE 2~ /ڿo u(>WK绸8o>*-j.^uBvYOΊ 'LIN24{scA'eevnZ=8O !IcS#|ሀ֬ !P#2 +FX)Y(ZHJ%țn=V\L}||lB13s+Wu11MM؁'wy{>cg8-ǢmC:N"`ȝ81U464>lU#$L b">T ҇GEDO'&^>J(?f-&CW+ 1(Y[")y*&$}[$m'-loL=^l]wyxz Il/{JP /, &<SA~>~0P'6 GW$!7ʚt: 5 }XClId:$! rZh/!de\059IXl`8K;D$`0]`#>bF#WźP*tLܼy$t@I~@: ק&Q.W\F{BɾILbN9P" 4)"{Db͹si)iMA8 y &/bzuR)$+w;(F^se#˜RjA>zlJ%h1r""b">^ѥD ciĐw rޠ {1D\ǜ)9wB #F^Ն>!֚s"h:b1 /7.<)JIоł;̎`͆,Liy!g!U\PMVFfMrQ덖!X:9?fD#X'?Z֬B3 byRaRDT$zðKOwEKӤh!董kw>3 f!\ۖ[ʯY!@8XX\qCXCG0CLNnݱK$ڪJ}}|C9ih WsNxz ml[PL;8jnn.4k`;662,ӈWJoyMuao5j 1t!+)>0 ܣjcn@b^]&:frFY]|,Y_5k r5\}HRツM&+k,!HF!Wd2EAf4;A@Rݺu =(bwU:~VN|>c7+kS5{oN-?_fvI BNmY0gm?gKf&~L#|Z,=O"¡a?oޡF؂+.JSw&!mѕ] wt9npD{HҤXa7 lg m>Eo3zݱ~K] =<'4D0DW1-M{sIM Xx27`j_z͑V5ѡBK:J/qaMB+< ^QUHp-9H1BpZ/):s܂os1==?zˉ|^o pܗgv?0"¬jy5F ^X m~i͑VhAǏ{ZZZhh3 VYS37 ԖMQjb,h h?P-fA#k^PW$4UÆᢪ'Nc}}} ֵSo;6mPH'D ȏ9RQQCNogh|ܼO_qsF^v Xݡa CDP`=[\\v`uAJg8T_P^^^TTԻwo  QQʳG~й’~uu)4V`)=:.q*nÇc w-ht)TL6%%%pbhnSHBPu@2/`). Note that this makes the image slightly more readable on e.g. HiDPI screens, but could also make the CAPTCHA potentially easier to break by a bot. Defaults to: True Rendering +++++++++ ``CaptchaTextInput`` supports the widget rendering using template introduced in Django 1.11. To change the output HTML, change the ``template_name`` to a custom template or modify ``get_context`` method to provide further context. See https://docs.djangoproject.com/en/dev/ref/forms/renderers/ for description of rendering API. Keep in mind that ``CaptchaTextInput`` is a subclass of ``MultiWidget`` which affects the context, see https://docs.djangoproject.com/en/2.0/ref/forms/widgets/#multiwidget. For example, you would:: class CustomCaptchaTextInput(CaptchaTextInput): template_name = 'custom_field.html' class CaptchaForm(forms.Form): captcha = CaptchaField(widget=CustomCaptchaTextInput) And then have a ``custom_field.html`` template:: {% load i18n %} {% spaceless %} {% endspaceless %} .. note:: For this to work, you MUST add ``django.forms`` to your ``INSTALLED_APPS`` and set ``FORM_RENDERER = 'django.forms.renderers.TemplatesSetting'`` in your settings.py. (See here_ for an explanation) .. _here: https://docs.djangoproject.com/en/2.0/ref/forms/renderers/#django.forms.renderers.TemplatesSetting Context ------- The following context variables are passed to the three "individual" templates: * ``image``: The URL of the rendered CAPTCHA image * ``name``: name of the field (i.e. the name of your form field) * ``key``: the hashed value (identifier) of this CAPTCHA: this is stored and passed in the hidden input * ``id``: the HTML ``id`` attribute to be used The ``captcha/field.html`` template receives the following context: * ``image``: the rendered (HTML) image and optionally audio elements * ``hidden_field``: the rendered hidden input * ``text_field``: the rendered text input Note: these elements have been marked as safe, you can render them straight into your template. .. _generators_ref: Generators and modifiers ++++++++++++++++++++++++ Random chars ------------ .. image:: _static/random_chars.png Classic captcha that picks four random chars. This is case insensitive. :: CAPTCHA_CHALLENGE_FUNCT = 'captcha.helpers.random_char_challenge' Simple Math ------------ .. image:: _static/math.png Another classic, that challenges the user to resolve a simple math challenge by randomly picking two numbers between one and nine, and a random operator among plus, minus, times. :: CAPTCHA_CHALLENGE_FUNCT = 'captcha.helpers.math_challenge' Dictionary Word ---------------- .. image:: _static/dict.png Picks a random word from a dictionary file. Note, you must define ``CAPTCHA_WORDS_DICTIONARY`` in your configuration to use this generator. :: CAPTCHA_CHALLENGE_FUNCT = 'captcha.helpers.word_challenge' Roll your own ------------- To have your own challenge generator, simply point ``CAPTCHA_CHALLENGE_FUNCT`` to a function that returns a tuple of strings: the first one (the challenge) will be rendered in the captcha, the second is the valid response to the challenge, e.g. ``('5+10=', '15')``, ``('AAAA', 'aaaa')`` This sample generator that returns six random digits:: import random def random_digit_challenge(): ret = u'' for i in range(6): ret += str(random.randint(0,9)) return ret, ret django-simple-captcha-0.6.2/docs/changes.rst000077700000000000000000000000001475735403000221542../CHANGESustar00rootroot00000000000000django-simple-captcha-0.6.2/docs/conf.py000066400000000000000000000146061475735403000201170ustar00rootroot00000000000000# Django Simple Captcha documentation build configuration file, created by # sphinx-quickstart on Sun Jul 10 12:35:54 2011. # # This file is execfile()d with the current directory set to its containing # dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # import sys # import os # sys.path.insert(0, '..') # import captcha # print captcha.get_version() # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.append(os.path.abspath('.')) # -- General configuration ---------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] # The suffix of source filenames. source_suffix = ".rst" # The encoding of source files. # source_encoding = 'utf-8' # The master toctree document. master_doc = "index" # General information about the project. project = "Django Simple Captcha" copyright = "2011-2025 Marco Bonetti and contributors" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = "0.6.2" # The full version, including alpha/beta/rc tags. release = version # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' # List of documents that shouldn't be included in the build. # unused_docs = [] # List of directories, relative to source directory, that shouldn't be searched # for source files. exclude_trees = ["_build"] # The reST default role (used for this markup: `text`) to use for all # documents. # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # -- Options for HTML output -------------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. html_theme = "sphinx_book_theme" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". # html_title = None # A shorter title for the navigation bar. Default is the same as html_title. # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ["_static"] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. # html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # html_additional_pages = {} # If false, no module index is generated. # html_use_modindex = True # If false, no index is generated. # html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # html_use_opensearch = '' # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = '' # Output file base name for HTML help builder. htmlhelp_basename = "DjangoSimpleCaptchadoc" # -- Options for LaTeX output ------------------------------------------------- # The paper size ('letter' or 'a4'). # latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). # latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, # documentclass [howto/manual]). latex_documents = [ ( "index", "DjangoSimpleCaptcha.tex", "Django Simple Captcha Documentation", "Marco Bonetti", "manual", ) ] # The name of an image file (relative to this directory) to place at the top of # the title page. # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # latex_use_parts = False # Additional stuff for the LaTeX preamble. # latex_preamble = '' # Documents to append as an appendix to all manuals. # latex_appendices = [] # If false, no module index is generated. # latex_use_modindex = True django-simple-captcha-0.6.2/docs/index.rst000066400000000000000000000017141475735403000204550ustar00rootroot00000000000000********************* Django Simple Captcha ********************* .. image:: https://travis-ci.org/mbi/django-simple-captcha.png?branch=master Django Simple Captcha is an extremely simple, yet highly customizable Django application to add captcha images to any Django form. .. image:: _static/captcha3.png Features ++++++++ * Very simple to setup and deploy, yet very configurable * Can use custom challenges (e.g. random chars, simple maths, 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 4.2+, Python3.9+ * A recent version of Pillow compiled with FreeType support * Flite is required for text-to-speech (audio) output, but not mandatory ****************** Contents: ****************** .. toctree:: :maxdepth: 2 usage.rst advanced.rst changes.rst django-simple-captcha-0.6.2/docs/requirements.txt000066400000000000000000000000221475735403000220670ustar00rootroot00000000000000sphinx-book-theme django-simple-captcha-0.6.2/docs/usage.rst000066400000000000000000000133271475735403000204550ustar00rootroot00000000000000Using django-simple-captcha =========================== Installation +++++++++++++ 1. Install ``django-simple-captcha`` via pip_: ``pip install django-simple-captcha`` 2. Add ``captcha`` to the ``INSTALLED_APPS`` in your ``settings.py`` 3. Run ``python manage.py migrate`` 4. Add an entry to your ``urls.py``:: urlpatterns += [ path('captcha/', include('captcha.urls')), ] .. _pip: http://pypi.python.org/pypi/pip Note: Pillow requires that image libraries are installed on your system. On e.g. Debian or Ubuntu, you'd need these packages to compile and install Pillow:: apt-get -y install libz-dev libjpeg-dev libfreetype6-dev python-dev Adding to a Form +++++++++++++++++ Using a ``CaptchaField`` is quite straight-forward: Define the Form ---------------- To embed a CAPTCHA in your forms, simply add a ``CaptchaField`` to the form definition:: from django import forms from captcha.fields import CaptchaField class CaptchaTestForm(forms.Form): myfield = AnyOtherField() captcha = CaptchaField() …or, in a ``ModelForm``:: from django import forms from captcha.fields import CaptchaField class CaptchaTestModelForm(forms.ModelForm): captcha = CaptchaField() class Meta: model = MyModel Validate the Form ----------------- In your view, validate the form as usual. If the user didn't provide a valid response to the CAPTCHA challenge, the form will raise a ``ValidationError`` and display an error message to the user:: def some_view(request): if request.POST: form = CaptchaTestForm(request.POST) # Validate the form: the captcha field will automatically # check the input if form.is_valid(): human = True else: form = CaptchaTestForm() return render(request, 'template.html', {'form': form}) Passing arguments to the field ------------------------------ ```` takes a few optional arguments: * ``id_prefix`` Optional prefix that will be added to the ID attribute in the generated fields and labels, to be used when e.g. several Captcha fields are being displayed on a same page. (added in version 0.4.4) * ``generator`` Optional callable or module path to callable that will be used to generate the challenge and the response, e.g. ``generator='path.to.generator_function'`` or ``generator=lambda: ('LOL', 'LOL')``, see also :ref:`generators_ref`. Defaults to whatever is defined in ``settings.CAPTCHA_CHALLENGE_FUNCT``. Example usage for ajax form --------------------------- An example CAPTCHA validation in AJAX:: from django.views.generic.edit import CreateView from captcha.models import CaptchaStore from captcha.helpers import captcha_image_url from django.http import HttpResponse import json class AjaxExampleForm(CreateView): template_name = '' form_class = AjaxForm def form_invalid(self, form): if self.request.is_ajax(): to_json_response = dict() to_json_response['status'] = 0 to_json_response['form_errors'] = form.errors to_json_response['new_cptch_key'] = CaptchaStore.generate_key() to_json_response['new_cptch_image'] = captcha_image_url(to_json_response['new_cptch_key']) return HttpResponse(json.dumps(to_json_response), content_type='application/json') def form_valid(self, form): form.save() if self.request.is_ajax(): to_json_response = dict() to_json_response['status'] = 1 to_json_response['new_cptch_key'] = CaptchaStore.generate_key() to_json_response['new_cptch_image'] = captcha_image_url(to_json_response['new_cptch_key']) return HttpResponse(json.dumps(to_json_response), content_type='application/json') And in javascript your must update the image and hidden input in form Example usage ajax refresh button --------------------------------- # html::
{{ form }}
# javascript:: $('.js-captcha-refresh').click(function(){ $form = $(this).parents('form'); $.getJSON($(this).data('url'), {}, function(json) { // This should update your captcha image src and captcha hidden input }); return false; }); Example usage ajax refresh --------------------------------- # javascript:: $('.captcha').click(function () { $.getJSON("/captcha/refresh/", function (result) { $('.captcha').attr('src', result['image_url']); $('#id_captcha_0').val(result['key']) }); }); Example usage in Django REST Framework --------------------------------- To use CAPTCHA in a serializer, simply have your serializer inherit from ``CaptchaSerializer``:: from captcha.serializers import CaptchaSerializer from rest_framework import serializers class CheckCaptchaSerializer(CaptchaSerializer): email = serializers.EmailField() …or use ``CaptchaModelSerializer``:: from captcha.serializers import CaptchaModelSerializer from rest_framework import serializers class CheckCaptchaModelSerializer(CaptchaModelSerializer): sender = serializers.EmailField() class Meta: model = User fields = ("captcha_code", "captcha_hashkey", "sender") ``CaptchaSerializer`` and ``CaptchaModelSerializer`` implement the ``captcha_code`` and ``captcha_hashkey`` fields. In case of invalid captcha or hash code an exception is raised:: raise exceptions.ValidationError({"error": gettext_lazy("Invalid CAPTCHA")}) django-simple-captcha-0.6.2/pyproject.toml000066400000000000000000000013571475735403000206030ustar00rootroot00000000000000[tool.isort] profile = "black" lines_after_imports = 2 multi_line_output = 3 order_by_type = true known_django = "django" known_django_third_party = "django_*" known_first_party = "apps,captcha" sections = "FUTURE,STDLIB,THIRDPARTY,DJANGO,DJANGO_THIRD_PARTY,FIRSTPARTY,LOCALFOLDER" skip_glob= "**/migrations/**" [tool.black] line-length = 88 target-version = ['py38'] exclude = ''' ( /( \.eggs # exclude a few common directories in the | \.git # root of the project | \.hg | \.mypy_cache | \.tox | \.venv | _build | buck-out | build | dist | node_modules )/ | foo.py # also separately exclude a file named foo.py in # the root of the project ) ''' django-simple-captcha-0.6.2/setup.cfg000066400000000000000000000000341475735403000174770ustar00rootroot00000000000000[bdist_wheel] universal = 1 django-simple-captcha-0.6.2/setup.py000066400000000000000000000042321475735403000173740ustar00rootroot00000000000000import sys from setuptools import find_packages, setup from setuptools.command.test import test as test_command from captcha import get_version as get_captcha_version class Tox(test_command): user_options = [("tox-args=", "a", "Arguments to pass to tox")] def initialize_options(self): test_command.initialize_options(self) self.tox_args = None def finalize_options(self): test_command.finalize_options(self) self.test_args = [] self.test_suite = True def run_tests(self): # import here, cause outside the eggs aren't loaded import shlex import tox args = self.tox_args if args: args = shlex.split(self.tox_args) errno = tox.cmdline(args=args) sys.exit(errno) install_requires = [ "Django >= 4.2", "Pillow >=6.2.0", "django-ranged-response == 0.2.0", ] EXTRAS_REQUIRE = {"test": ("testfixtures",)} with open("README.rst") as readme: long_description = readme.read() setup( name="django-simple-captcha", version=get_captcha_version(), description="A very simple, yet powerful, Django captcha application", long_description=long_description, author="Marco Bonetti", author_email="mbonetti@gmail.com", url="https://github.com/mbi/django-simple-captcha", license="MIT", packages=find_packages(exclude=["testproject", "testproject.*"]), classifiers=[ "Development Status :: 4 - Beta", "Environment :: Web Environment", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Topic :: Security", "Topic :: Internet :: WWW/HTTP", "Framework :: Django", ], include_package_data=True, zip_safe=False, install_requires=install_requires, extras_require=EXTRAS_REQUIRE, tests_require=["tox~=4.11.4"], cmdclass={"test": Tox}, ) django-simple-captcha-0.6.2/testproject/000077500000000000000000000000001475735403000202275ustar00rootroot00000000000000django-simple-captcha-0.6.2/testproject/.coveragerc000066400000000000000000000002511475735403000223460ustar00rootroot00000000000000[run] branch = True source = captcha omit = ../captcha/migrations/* ../captcha/tests/* ../captcha/management/* [report] precision = 2 fail_under = 84.0 django-simple-captcha-0.6.2/testproject/__init__.py000066400000000000000000000000001475735403000223260ustar00rootroot00000000000000django-simple-captcha-0.6.2/testproject/coverage.sh000066400000000000000000000002701475735403000223550ustar00rootroot00000000000000#!/bin/bash export CAPTCHA_FLITE_PATH=`which flite` export CAPTCHA_SOX_PATH=`which sox` coverage run --rcfile .coveragerc manage.py test --failfast captcha coverage xml coverage html django-simple-captcha-0.6.2/testproject/drf_settings.py000066400000000000000000000001351475735403000232730ustar00rootroot00000000000000from testproject.settings import * # noqa INSTALLED_APPS.append("rest_framework") # noqa django-simple-captcha-0.6.2/testproject/forms.py000066400000000000000000000005421475735403000217300ustar00rootroot00000000000000from django import forms from captcha.fields import CaptchaField, CaptchaTextInput class CustomCaptchaTextInput(CaptchaTextInput): template_name = "captcha_test/custom_captcha_field.html" class CaptchaForm(forms.Form): captcha = CaptchaField() class CustomCaptchaForm(forms.Form): captcha = CaptchaField(widget=CustomCaptchaTextInput) django-simple-captcha-0.6.2/testproject/jinja2_settings.py000066400000000000000000000001351475735403000236750ustar00rootroot00000000000000from testproject.settings import * # noqa FORM_RENDERER = "django.forms.renderers.Jinja2" django-simple-captcha-0.6.2/testproject/manage.py000066400000000000000000000017631475735403000220400ustar00rootroot00000000000000#!/usr/bin/env python import os import sys try: from django.core.management import execute_manager OLD_DJANGO = True except ImportError: from django.core.management import execute_from_command_line OLD_DJANGO = False if OLD_DJANGO: try: import settings # Assumed to be in the same directory. except ImportError: sys.stderr.write( "Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__ ) sys.exit(1) BASEDIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) sys.path.insert(0, BASEDIR) if __name__ == "__main__": os.environ["DJANGO_SETTINGS_MODULE"] = "testproject.settings" if OLD_DJANGO: execute_manager(settings) else: execute_from_command_line(sys.argv) django-simple-captcha-0.6.2/testproject/settings.py000066400000000000000000000042531475735403000224450ustar00rootroot00000000000000import os import sys SITE_ID = 1 PROJECT_PATH = os.path.abspath(os.path.dirname(__file__)) PYTHON_VERSION = "%s.%s" % sys.version_info[:2] DATABASES = { "default": { "ENGINE": "django.db.backends.sqlite3", "NAME": os.path.join(PROJECT_PATH, "django-simple-captcha.db"), } } # TEST_DATABASE_CHARSET = "utf8" # TEST_DATABASE_COLLATION = "utf8_general_ci" DATABASE_SUPPORTS_TRANSACTIONS = True INSTALLED_APPS = [ "django.contrib.auth", "django.contrib.admin", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.sites", "django.forms", "django.contrib.messages", # "rest_framework", "captcha", ] FORM_RENDERER = "django.forms.renderers.TemplatesSetting" LANGUAGE_CODE = "en" LANGUAGES = ( ("en", "English"), # ('ja', u('日本語')), ) FIXTURE_DIRS = (os.path.join(PROJECT_PATH, "fixtures"),) ROOT_URLCONF = "testproject.urls" DEBUG = True TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", "APP_DIRS": True, "OPTIONS": { "debug": False, "context_processors": ( "django.template.context_processors.request", "django.contrib.auth.context_processors.auth", "django.template.context_processors.debug", "django.template.context_processors.i18n", "django.template.context_processors.media", "django.template.context_processors.static", "django.template.context_processors.tz", "django.contrib.messages.context_processors.messages", ), }, "DIRS": ("templates",), } ] USE_TZ = True SECRET_KEY = "empty" MIDDLEWARE = ( "django.contrib.sessions.middleware.SessionMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", ) CAPTCHA_FLITE_PATH = os.environ.get("CAPTCHA_FLITE_PATH", None) CAPTCHA_SOX_PATH = os.environ.get("CAPTCHA_SOX_PATH", None) CAPTCHA_BACKGROUND_COLOR = "transparent" CAPTCHA_LETTER_COLOR_FUNCT = "captcha.helpers.random_letter_color_challenge" CAPTCHA_BACKGROUND_COLOR = "#ffffff" django-simple-captcha-0.6.2/testproject/templates/000077500000000000000000000000001475735403000222255ustar00rootroot00000000000000django-simple-captcha-0.6.2/testproject/templates/captcha_test/000077500000000000000000000000001475735403000246675ustar00rootroot00000000000000django-simple-captcha-0.6.2/testproject/templates/captcha_test/custom_captcha_field.html000066400000000000000000000010101475735403000317050ustar00rootroot00000000000000{% load i18n %} {% spaceless %}
{% endspaceless %} django-simple-captcha-0.6.2/testproject/templates/captcha_test/image.html000066400000000000000000000003661475735403000266440ustar00rootroot00000000000000{% load i18n %} {% spaceless %} {% if audio %}{% endif %}captcha-template-test{% if audio %}{% endif %} {% endspaceless %} django-simple-captcha-0.6.2/testproject/templates/captcha_test/image_html5_audio.html000066400000000000000000000007131475735403000311320ustar00rootroot00000000000000{% load i18n %} {% spaceless %} {% if audio %} play audio open audio {% endif %} {% endspaceless %} django-simple-captcha-0.6.2/testproject/templates/home.html000066400000000000000000000003421475735403000240420ustar00rootroot00000000000000
{% csrf_token %} {{form.captcha.errors}} {{form.captcha}}
django-simple-captcha-0.6.2/testproject/urls.py000066400000000000000000000002721475735403000215670ustar00rootroot00000000000000from django.urls import include from django.urls import re_path as url from .views import home urlpatterns = [ url(r"^$", home), url(r"^captcha/", include("captcha.urls")), ] django-simple-captcha-0.6.2/testproject/views.py000066400000000000000000000006221475735403000217360ustar00rootroot00000000000000from forms import CaptchaForm # CustomCaptchaForm from django.http import HttpResponseRedirect from django.shortcuts import render def home(request): if request.POST: form = CaptchaForm(request.POST) if form.is_valid(): return HttpResponseRedirect(request.path + "?ok") else: form = CaptchaForm() return render(request, "home.html", {"form": form}) django-simple-captcha-0.6.2/tox.ini000066400000000000000000000073731475735403000172060ustar00rootroot00000000000000 [tox] envlist = py{39,310,311}-django42, py{310,311,312}-django{50,51}, py{312,313}-django52, py{39,310}-django42-jinja2, py{310,311}-django{42,50}-jinja2, py312-django{50,51}-jinja2, py313-django52 py313-django52-drf gettext,flake8,docs,coverage [gh-actions] python = 3.13: py313-django52, py313-django52-drf 3.12: py312-django50, py312-django51, py312-django52, py312-django51-jinja2 3.11: p311-django-51, p311-django-50, p311-django-50-jinjia, p311-django-42, p311-django-42-jinjia 3.10: py310-django42, py310-django42-jinja2, py310-django50, py310-django51, py310-django50-jinja2 3.9: py39-django42, py39-django42-jinja2 [testenv] changedir = testproject commands = python -Wd manage.py test captcha setenv = PYTHONDONTWRITEBYTECODE=1 deps = django42: Django>=4.2a,<4.3 django50: Django>=5.0a,<5.1 django51: Django>=5.1,<5.2 django52: Django>=5.2a1,<5.3 py{39,310,311,312, 313}-django{42,50,51,52}: python3-memcached py313-django52-drf: djangorestframework jinja2 Pillow extras = test [testenv:py39-django42-jinja2] commands = python -Wd manage.py test captcha --settings jinja2_settings [testenv:py310-django42-jinja2] commands = python -Wd manage.py test captcha --settings jinja2_settings [testenv:py310-django50-jinja2] commands = python -Wd manage.py test captcha --settings jinja2_settings [testenv:py311-django50-jinja2] commands = python -Wd manage.py test captcha --settings jinja2_settings [testenv:py312-django50-jinja2] commands = python -Wd manage.py test captcha --settings jinja2_settings [testenv:py313-django52-drf] commands = python -Wd manage.py test captcha --settings drf_settings [testenv:gettext] basepython = python3.12 changedir = captcha/locale/ allowlist_externals = msgfmt commands = msgfmt -c -o bg/LC_MESSAGES/django.mo bg/LC_MESSAGES/django.po msgfmt -c -o cs/LC_MESSAGES/django.mo cs/LC_MESSAGES/django.po msgfmt -c -o de/LC_MESSAGES/django.mo de/LC_MESSAGES/django.po msgfmt -c -o en/LC_MESSAGES/django.mo en/LC_MESSAGES/django.po msgfmt -c -o es/LC_MESSAGES/django.mo es/LC_MESSAGES/django.po msgfmt -c -o fa/LC_MESSAGES/django.mo fa/LC_MESSAGES/django.po msgfmt -c -o fr/LC_MESSAGES/django.mo fr/LC_MESSAGES/django.po msgfmt -c -o it/LC_MESSAGES/django.mo it/LC_MESSAGES/django.po msgfmt -c -o ja/LC_MESSAGES/django.mo ja/LC_MESSAGES/django.po msgfmt -c -o nl/LC_MESSAGES/django.mo nl/LC_MESSAGES/django.po msgfmt -c -o pl/LC_MESSAGES/django.mo pl/LC_MESSAGES/django.po msgfmt -c -o pt_BR/LC_MESSAGES/django.mo pt_BR/LC_MESSAGES/django.po msgfmt -c -o ru/LC_MESSAGES/django.mo ru/LC_MESSAGES/django.po msgfmt -c -o sk/LC_MESSAGES/django.mo sk/LC_MESSAGES/django.po msgfmt -c -o sv/LC_MESSAGES/django.mo sv/LC_MESSAGES/django.po msgfmt -c -o tr/LC_MESSAGES/django.mo tr/LC_MESSAGES/django.po msgfmt -c -o uk/LC_MESSAGES/django.mo uk/LC_MESSAGES/django.po msgfmt -c -o zh_CN/LC_MESSAGES/django.mo zh_CN/LC_MESSAGES/django.po msgfmt -c -o zh_Hans/LC_MESSAGES/django.mo zh_Hans/LC_MESSAGES/django.po [testenv:flake8] basepython = python3.12 deps = flake8 commands= flake8 {toxinidir}/captcha [testenv:docs] basepython = python3.12 deps = sphinx sphinx-book-theme changedir = docs commands= sphinx-build -b html . _build/html [testenv:coverage] changedir = testproject passenv = CAPTCHA_FLITE_PATH CAPTCHA_SOX_PATH deps = coverage djangorestframework commands= # coverage debug config coverage run --rcfile=.coveragerc manage.py test captcha --settings drf_settings coverage report --rcfile=.coveragerc