Initial commit
This commit is contained in:
commit
64269ee1f8
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Hopsenn
|
||||
|
||||
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.
|
5
MANIFEST.in
Normal file
5
MANIFEST.in
Normal file
@ -0,0 +1,5 @@
|
||||
include README.md
|
||||
include LICENSE
|
||||
recursive-include django_hoptcha/static *
|
||||
recursive-include django_hoptcha/templates *
|
||||
recursive-include django_hoptcha/templatetags *.py
|
215
README.md
Normal file
215
README.md
Normal file
@ -0,0 +1,215 @@
|
||||
# Hoptcha Client for Django
|
||||
|
||||
Hoptcha is a modern CAPTCHA provider designed for seamless integration into web applications.
|
||||
This package, `django-hoptcha`, allows Django developers to quickly integrate Hoptcha CAPTCHA validation into their views with minimal effort.
|
||||
|
||||
---
|
||||
|
||||
## 📚 Table of Contents
|
||||
|
||||
* [Features](#features)
|
||||
* [Installation](#installation)
|
||||
* [Quick Start](#quick-start)
|
||||
|
||||
* [1. Configure settings](#1-configure-settings)
|
||||
* [2. Protect a view with CAPTCHA](#2-protect-a-view-with-captcha)
|
||||
* [3. Use JavaScript integration](#3-use-javascript-integration)
|
||||
* [Advanced Usage](#advanced-usage)
|
||||
|
||||
* [Custom Rate-Limiting Logic](#custom-rate-limiting-logic)
|
||||
* [Combining with Django-Ratelimit](#combining-with-django-ratelimit)
|
||||
* [Custom CAPTCHA UI Rendering](#custom-captcha-ui-rendering)
|
||||
* [Customization](#customization)
|
||||
* [License](#license)
|
||||
|
||||
---
|
||||
|
||||
## ✅ Features
|
||||
|
||||
* 🔒 Server-side CAPTCHA verification with client and secret keys
|
||||
* 🧠 Rate-limiting and conditional CAPTCHA fallback
|
||||
* 🤩 Easy integration via decorators
|
||||
* ⚙️ Customizable request identification (e.g., IP + user ID)
|
||||
* 🧰 Built-in support for Django views and REST APIs
|
||||
* 🛆 Lightweight and production-ready
|
||||
* 🎨 Custom iframe rendering with `{% captcha_iframe %}`
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Installation
|
||||
|
||||
```bash
|
||||
pip install django-hoptcha
|
||||
```
|
||||
|
||||
Add to `INSTALLED_APPS` in `settings.py`:
|
||||
|
||||
```python
|
||||
INSTALLED_APPS = [
|
||||
...,
|
||||
'django_hoptcha',
|
||||
]
|
||||
```
|
||||
|
||||
Collect static files:
|
||||
|
||||
```bash
|
||||
python manage.py collectstatic
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### 1. Configure settings
|
||||
|
||||
Add the following to your `settings.py`:
|
||||
|
||||
```python
|
||||
CAPTCHA_VERIFY_URL = 'https://your-hoptcha-domain.com/captcha/validate/'
|
||||
CAPTCHA_GENERATE_URL = 'https://your-hoptcha-domain.com/captcha/'
|
||||
CAPTCHA_PUBLIC_KEY = 'your-client-key'
|
||||
CAPTCHA_PRIVATE_KEY = 'your-secret-key'
|
||||
```
|
||||
|
||||
> These credentials are provided by the Hoptcha service when you register your application.
|
||||
|
||||
---
|
||||
|
||||
### 2. Protect a view with CAPTCHA
|
||||
|
||||
Import the decorator and apply it to any Django view:
|
||||
|
||||
```python
|
||||
from django_hoptcha.decorators import hoptcha_required
|
||||
|
||||
@hoptcha_required()
|
||||
def my_view(request):
|
||||
return JsonResponse({'success': True})
|
||||
```
|
||||
|
||||
This will:
|
||||
|
||||
* Track repeated requests using the IP address.
|
||||
* Require CAPTCHA after 5 attempts (default).
|
||||
* Verify the token via the configured Hoptcha endpoint.
|
||||
|
||||
---
|
||||
|
||||
### 3. Use JavaScript integration
|
||||
|
||||
In your template:
|
||||
|
||||
```html
|
||||
{% load static hoptcha_tags %}
|
||||
{% captcha_placeholder %}
|
||||
<script src="{% static 'django_hoptcha/hoptcha.js' %}"></script>
|
||||
```
|
||||
|
||||
Or if you want to place captcha placeholder dynamically use `<div id="hoptcha-container"></div>`.
|
||||
Alternatively you can use `{% captcha_iframe %}` if your captcha is static.
|
||||
|
||||
|
||||
In your frontend JS:
|
||||
|
||||
```javascript
|
||||
function startPasswordReset() {
|
||||
const payload = {
|
||||
studentId: String(studentId),
|
||||
captcha_token: 'initial'
|
||||
};
|
||||
|
||||
hoptchaPost('/users/send-reset/', payload, function() {
|
||||
renderResetOTPEntry();
|
||||
}, function(error) {
|
||||
showError(error);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
You may optionally pass a custom onCaptcha renderer if you want to override the default iframe design:
|
||||
|
||||
```javascript
|
||||
hoptchaPost('/users/send-reset/', payload, onSuccess, onError, function customCaptchaRenderer(url, key, callback) {
|
||||
// Your custom UI logic here
|
||||
showCustomModal();
|
||||
renderMyCustomCaptchaIframe(url, key);
|
||||
window._captchaSuccessCallback = callback;
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Advanced Usage
|
||||
|
||||
### Custom Rate-Limiting Logic
|
||||
|
||||
You can customize how repeated attempts are tracked using a key function:
|
||||
|
||||
```python
|
||||
def ip_and_student_id(request):
|
||||
ip = request.META.get("REMOTE_ADDR", "unknown")
|
||||
student_id = request.POST.get("studentId", "")
|
||||
return f"{ip}:{student_id}"
|
||||
```
|
||||
|
||||
Apply it like this:
|
||||
|
||||
```python
|
||||
@hoptcha_required(threshold=5, timeout=300, key_func=ip_and_student_id)
|
||||
def send_reset_code(request):
|
||||
...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Combining with Django-Ratelimit
|
||||
|
||||
This package works seamlessly with [`django-ratelimit`](https://pypi.org/project/django-ratelimit/):
|
||||
|
||||
```python
|
||||
from ratelimit.decorators import ratelimit
|
||||
from django_hoptcha.decorators import hoptcha_required
|
||||
|
||||
@ratelimit(key='ip', rate='15/m', method='POST', block=True)
|
||||
@hoptcha_required(threshold=5, timeout=300)
|
||||
def secure_view(request):
|
||||
...
|
||||
```
|
||||
|
||||
The `@ratelimit` decorator enforces strict rate limits while `@hoptcha_required` provides a soft CAPTCHA fallback.
|
||||
|
||||
### Custom CAPTCHA UI Rendering
|
||||
|
||||
You can override the default iframe and style using the optional onCaptcha parameter in the hoptchaPost() JavaScript function. This is useful if you want to match your app’s branding or use modals.
|
||||
|
||||
```javascript
|
||||
hoptchaPost('/endpoint', payload, onSuccess, onError, function renderCustom(url, key, cb) {
|
||||
// Replace container with your custom implementation
|
||||
$('#myCustomCaptchaArea').html(`<iframe src="${url}?client_key=${key}"></iframe>`);
|
||||
window._captchaSuccessCallback = cb;
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Customization
|
||||
|
||||
| Parameter | Description | Default |
|
||||
| ----------- | ----------------------------------------------------- | ------------- |
|
||||
| `threshold` | Number of allowed attempts before CAPTCHA is required | `5` |
|
||||
| `timeout` | Time in seconds to reset attempt count | `300` (5 min) |
|
||||
| `key_func` | Function to identify requestor (IP, user ID, etc.) | IP address |
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 📄 License
|
||||
|
||||
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
|
||||
|
||||
---
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
Contributions are welcome! Open issues or pull requests on [GitHub](https://github.com/yourusername/django-hoptcha).
|
23
django_hoptcha/__init__.py
Normal file
23
django_hoptcha/__init__.py
Normal file
@ -0,0 +1,23 @@
|
||||
"""
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Hopsenn
|
||||
|
||||
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.
|
||||
"""
|
60
django_hoptcha/decorators.py
Normal file
60
django_hoptcha/decorators.py
Normal file
@ -0,0 +1,60 @@
|
||||
"""
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Hopsenn
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
from functools import wraps
|
||||
from django.core.cache import cache
|
||||
from django.http import JsonResponse
|
||||
from .validators import verify_token
|
||||
from .settings import GENERATE_URL, PUBLIC_KEY
|
||||
|
||||
def default_key_func(request):
|
||||
return request.META.get('REMOTE_ADDR', 'unknown-ip')
|
||||
|
||||
def hoptcha_required(threshold=5, timeout=300, key_func=default_key_func):
|
||||
"""
|
||||
Decorator that applies CAPTCHA verification after `threshold` failed attempts,
|
||||
tracked via cache using a customizable `key_func`.
|
||||
"""
|
||||
|
||||
def decorator(view_func):
|
||||
@wraps(view_func)
|
||||
def _wrapped_view(request, *args, **kwargs):
|
||||
key = key_func(request)
|
||||
cache_key = f"hoptcha-attempts:{key}"
|
||||
attempts = cache.get(cache_key, 0)
|
||||
|
||||
if attempts >= threshold:
|
||||
token = request.POST.get("captcha_token") or request.GET.get("captcha_token")
|
||||
if not token or not verify_token(token):
|
||||
return JsonResponse({
|
||||
"error": "CAPTCHA",
|
||||
"url": GENERATE_URL,
|
||||
"key": PUBLIC_KEY
|
||||
}, status=400)
|
||||
|
||||
# Increment and store attempt count unless the view says otherwise
|
||||
cache.set(cache_key, attempts + 1, timeout=timeout)
|
||||
return view_func(request, *args, **kwargs)
|
||||
return _wrapped_view
|
||||
return decorator
|
33
django_hoptcha/settings.py
Normal file
33
django_hoptcha/settings.py
Normal file
@ -0,0 +1,33 @@
|
||||
"""
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Hopsenn
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
def get(key, default=None):
|
||||
return getattr(settings, key, default)
|
||||
|
||||
VERIFY_URL = get('CAPTCHA_VERIFY_URL', 'https://hoptcha.com/captcha/validate/')
|
||||
GENERATE_URL = get('CAPTCHA_GENERATE_URL', 'https://hoptcha.com/captcha/')
|
||||
PUBLIC_KEY = get('CAPTCHA_PUBLIC_KEY', '')
|
||||
PRIVATE_KEY = get('CAPTCHA_PRIVATE_KEY', '')
|
70
django_hoptcha/static/django_hoptcha/hoptcha.js
Normal file
70
django_hoptcha/static/django_hoptcha/hoptcha.js
Normal file
@ -0,0 +1,70 @@
|
||||
// ==================== HOPTCHA CLIENT ====================
|
||||
|
||||
(function(window, $) {
|
||||
if (!$) {
|
||||
console.error("Hoptcha requires jQuery. Please include it before this script.");
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle messages from the iframe.
|
||||
*/
|
||||
window.addEventListener("message", function (event) {
|
||||
const { token } = event.data || {};
|
||||
|
||||
if (token && typeof window._captchaSuccessCallback === 'function') {
|
||||
const callback = window._captchaSuccessCallback;
|
||||
window._captchaSuccessCallback = null; // clear after use
|
||||
callback(token); // Send token to original requester
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Renders the Hoptcha iframe inside #hoptcha-container.
|
||||
* @param {string} captcha_url - The Hoptcha URL endpoint.
|
||||
* @param {string} client_key - The client public key.
|
||||
* @param {function} onSuccessCallback - Called with token when CAPTCHA is solved.
|
||||
*/
|
||||
window.renderCaptchaStep = function(captcha_url, client_key, onSuccessCallback) {
|
||||
$('#hoptcha-container').html(`
|
||||
<iframe
|
||||
id="captcha-iframe"
|
||||
src="${captcha_url}?client_key=${client_key}"
|
||||
style="width: 100%; height: 250px; border: none; border-radius: 12px;"
|
||||
></iframe>
|
||||
`);
|
||||
|
||||
// Register callback
|
||||
window._captchaSuccessCallback = onSuccessCallback;
|
||||
};
|
||||
|
||||
/**
|
||||
* Utility for retrying failed requests that require CAPTCHA.
|
||||
* @param {string} url - The POST endpoint.
|
||||
* @param {object} payload - Payload to send, must include `captcha_token`.
|
||||
* @param {function} onSuccess - Success handler.
|
||||
* @param {function} [onError] - Error fallback.
|
||||
*/
|
||||
window.hoptchaPost = function(url, payload, onSuccess, onError, onCaptcha) {
|
||||
$.post(url, payload, function(data) {
|
||||
if (onSuccess) onSuccess(data);
|
||||
}).fail(xhr => {
|
||||
const error = xhr.responseJSON ? xhr.responseJSON.error : 'Something went wrong.';
|
||||
const captcha_url = xhr.responseJSON?.url;
|
||||
const client_key = xhr.responseJSON?.key;
|
||||
|
||||
if (!captcha_url || !client_key) {
|
||||
if (onError) onError(error);
|
||||
else console.error(error);
|
||||
return;
|
||||
}
|
||||
|
||||
const render = onCaptcha || window.renderCaptchaStep;
|
||||
render(captcha_url, client_key, function(token) {
|
||||
payload.captcha_token = token;
|
||||
window.hoptchaPost(url, payload, onSuccess, onError, onCaptcha);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
})(window, window.jQuery);
|
@ -0,0 +1,7 @@
|
||||
<div id="hoptcha-container">
|
||||
<iframe
|
||||
src="{{ captcha_url }}?client_key={{ public_key }}"
|
||||
style="width: 100%; height: 250px; border: none; border-radius: 12px;">
|
||||
</iframe>
|
||||
</div>
|
||||
<script src="{% static 'django_hoptcha/hoptcha.js' %}"></script>
|
@ -0,0 +1 @@
|
||||
<div id="hoptcha-container"></div>
|
23
django_hoptcha/templatetags/__init__.py
Normal file
23
django_hoptcha/templatetags/__init__.py
Normal file
@ -0,0 +1,23 @@
|
||||
"""
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Hopsenn
|
||||
|
||||
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.
|
||||
"""
|
44
django_hoptcha/templatetags/hoptcha_tags.py
Normal file
44
django_hoptcha/templatetags/hoptcha_tags.py
Normal file
@ -0,0 +1,44 @@
|
||||
from django import template
|
||||
from django.conf import settings
|
||||
|
||||
"""
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Hopsenn
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def captcha_placeholder():
|
||||
return render_to_string('django_hoptcha/captcha_placeholder.html', {})
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def captcha_iframe():
|
||||
context = {
|
||||
'captcha_url': getattr(settings, 'CAPTCHA_GENERATE_URL', '#'),
|
||||
'public_key': getattr(settings, 'CAPTCHA_PUBLIC_KEY', ''),
|
||||
}
|
||||
return render_to_string('django_hoptcha/captcha_iframe.html', context)
|
50
django_hoptcha/validators.py
Normal file
50
django_hoptcha/validators.py
Normal file
@ -0,0 +1,50 @@
|
||||
"""
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Hopsenn
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
import json
|
||||
import requests
|
||||
|
||||
from .settings import (
|
||||
VERIFY_URL,
|
||||
PUBLIC_KEY,
|
||||
PRIVATE_KEY
|
||||
)
|
||||
|
||||
|
||||
def verify_token(token):
|
||||
try:
|
||||
payload = {
|
||||
"token": token,
|
||||
"client_key": PUBLIC_KEY,
|
||||
"client_secret": PRIVATE_KEY,
|
||||
}
|
||||
headers = {"Content-Type": "application/json"}
|
||||
response = requests.post(VERIFY_URL, data=json.dumps(payload), headers=headers, timeout=5)
|
||||
|
||||
if response.status_code == 200:
|
||||
return response.json().get("success", False)
|
||||
return False
|
||||
|
||||
except Exception:
|
||||
return False
|
23
example/demoapp/demoapp/__init__.py
Normal file
23
example/demoapp/demoapp/__init__.py
Normal file
@ -0,0 +1,23 @@
|
||||
"""
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Hopsenn
|
||||
|
||||
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.
|
||||
"""
|
31
example/demoapp/demoapp/asgi.py
Normal file
31
example/demoapp/demoapp/asgi.py
Normal file
@ -0,0 +1,31 @@
|
||||
"""
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Hopsenn
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.asgi import get_asgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'demoapp.settings')
|
||||
|
||||
application = get_asgi_application()
|
141
example/demoapp/demoapp/settings.py
Normal file
141
example/demoapp/demoapp/settings.py
Normal file
@ -0,0 +1,141 @@
|
||||
"""
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Hopsenn
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = 'django-insecure-jv__th8%4!0ite+vhv%c@(q$1=n8xwg(46@k3zbz7^oeq-+)p&'
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = True
|
||||
|
||||
ALLOWED_HOSTS = []
|
||||
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'django_hoptcha'
|
||||
]
|
||||
|
||||
CAPTCHA_VERIFY_URL = 'http://127.0.0.1:8000/captcha/validate/'
|
||||
CAPTCHA_GENERATE_URL = 'http://127.0.0.1:8000/captcha/'
|
||||
CAPTCHA_PUBLIC_KEY = 'Pfv5wvutNRpeg75dTNyCE5VXozyhCnqVHWPtiRSl'
|
||||
CAPTCHA_PRIVATE_KEY = 'l-qt4GG5Uz4MDJ2Y-62-55I5dtSgSarWaps_pz2F6CjHbvIEopxpl_DaiJv1wzxwscJxqrYaHnV7Tvb70zAmDw'
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'demoapp.urls'
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [BASE_DIR / 'demoapp'],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
WSGI_APPLICATION = 'demoapp.wsgi.application'
|
||||
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/5.2/ref/settings/#databases
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': BASE_DIR / 'db.sqlite3',
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/5.2/topics/i18n/
|
||||
|
||||
LANGUAGE_CODE = 'en-us'
|
||||
|
||||
TIME_ZONE = 'UTC'
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
USE_TZ = True
|
||||
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/5.2/howto/static-files/
|
||||
|
||||
STATIC_URL = 'static/'
|
||||
STATIC_ROOT = BASE_DIR / 'staticfiles'
|
||||
|
||||
# Default primary key field type
|
||||
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field
|
||||
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
98
example/demoapp/demoapp/templates/form.html
Normal file
98
example/demoapp/demoapp/templates/form.html
Normal file
@ -0,0 +1,98 @@
|
||||
{% load static %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Hoptcha Demo</title>
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
<script src="{% static 'django_hoptcha/hoptcha.js' %}"></script>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 20px;
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
h2 { color: #333; }
|
||||
nav {
|
||||
background-color: #222;
|
||||
padding: 10px;
|
||||
}
|
||||
nav a {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
margin: 0 10px;
|
||||
}
|
||||
form {
|
||||
background: white;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
max-width: 400px;
|
||||
}
|
||||
.my-button, input[type="submit"] {
|
||||
padding: 10px 20px;
|
||||
background-color: #333;
|
||||
color: white;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
.my-button:hover, input[type="submit"]:hover {
|
||||
background-color: #555;
|
||||
}
|
||||
input[type="text"], input[type="password"], input[type="date"], select {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
margin: 6px 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
#custom-captcha-modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 20%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: white;
|
||||
border: 1px solid #ccc;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
z-index: 1000;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Custom CAPTCHA Form</h1>
|
||||
<form id="demo-form">
|
||||
<input type="text" name="name" placeholder="Your name" required>
|
||||
<button class="my-button" type="submit">Submit</button>
|
||||
</form>
|
||||
|
||||
<div id="custom-captcha-modal">
|
||||
<h3>Verify You're Human</h3>
|
||||
<iframe id="custom-captcha-frame" style="width: 100%; height: 250px; border: none;"></iframe>
|
||||
<button class="my-button" onclick="closeCaptcha()">Cancel</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function closeCaptcha() {
|
||||
$('#custom-captcha-modal').hide();
|
||||
}
|
||||
|
||||
$("#demo-form").submit(function(e) {
|
||||
e.preventDefault();
|
||||
const formData = $(this).serializeArray();
|
||||
let payload = {};
|
||||
formData.forEach(item => payload[item.name] = item.value);
|
||||
payload["captcha_token"] = "init";
|
||||
|
||||
hoptchaPost('/submit/', payload, function(data) {
|
||||
alert(data.success);
|
||||
}, function(error) {
|
||||
alert(error);
|
||||
}, function renderCustomCaptcha(url, key, cb) {
|
||||
$('#custom-captcha-frame').attr('src', url + '?client_key=' + key);
|
||||
window._captchaSuccessCallback = cb;
|
||||
$('#custom-captcha-modal').show();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
39
example/demoapp/demoapp/urls.py
Normal file
39
example/demoapp/demoapp/urls.py
Normal file
@ -0,0 +1,39 @@
|
||||
"""
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Hopsenn
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
from django.contrib import admin
|
||||
from django.urls import path
|
||||
from django.conf import settings
|
||||
from django.conf.urls.static import static
|
||||
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path('admin/', admin.site.urls),
|
||||
path('', views.form_index, name='form_index'),
|
||||
path('submit/', views.protected_form_submit, name='protected_form_submit'),
|
||||
]
|
||||
|
||||
if settings.DEBUG:
|
||||
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
39
example/demoapp/demoapp/views.py
Normal file
39
example/demoapp/demoapp/views.py
Normal file
@ -0,0 +1,39 @@
|
||||
"""
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Hopsenn
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
from django.http import JsonResponse
|
||||
from django.shortcuts import render
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django_hoptcha.decorators import hoptcha_required
|
||||
|
||||
@csrf_exempt
|
||||
@hoptcha_required(threshold=3, timeout=300)
|
||||
def protected_form_submit(request):
|
||||
name = request.POST.get("name")
|
||||
if not name:
|
||||
return JsonResponse({"error": "Name is required."}, status=400)
|
||||
return JsonResponse({"success": f"Hello, {name}!"})
|
||||
|
||||
def form_index(request):
|
||||
return render(request, "templates/form.html")
|
31
example/demoapp/demoapp/wsgi.py
Normal file
31
example/demoapp/demoapp/wsgi.py
Normal file
@ -0,0 +1,31 @@
|
||||
"""
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Hopsenn
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'demoapp.settings')
|
||||
|
||||
application = get_wsgi_application()
|
44
example/demoapp/manage.py
Executable file
44
example/demoapp/manage.py
Executable file
@ -0,0 +1,44 @@
|
||||
"""
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Hopsenn
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def main():
|
||||
"""Run administrative tasks."""
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'demoapp.settings')
|
||||
try:
|
||||
from django.core.management import execute_from_command_line
|
||||
except ImportError as exc:
|
||||
raise ImportError(
|
||||
"Couldn't import Django. Are you sure it's installed and "
|
||||
"available on your PYTHONPATH environment variable? Did you "
|
||||
"forget to activate a virtual environment?"
|
||||
) from exc
|
||||
execute_from_command_line(sys.argv)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
49
setup.py
Normal file
49
setup.py
Normal file
@ -0,0 +1,49 @@
|
||||
"""
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Hopsenn
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name='django-hoptcha',
|
||||
version='1.0.0',
|
||||
description='Django client integration for the Hoptcha CAPTCHA provider',
|
||||
author='Hopsenn',
|
||||
author_email='ivan.nikolskiy@hopsenn.com',
|
||||
url='https://git.hopsenn.com/hopsenn/django-hoptcha',
|
||||
packages=find_packages(),
|
||||
include_package_data=True,
|
||||
install_requires=[
|
||||
'requests>=2.20',
|
||||
'Django>=3.2'
|
||||
],
|
||||
classifiers=[
|
||||
'Development Status :: 4 - Beta',
|
||||
'Framework :: Django',
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: MIT License',
|
||||
'Operating System :: OS Independent',
|
||||
'Programming Language :: Python :: 3',
|
||||
],
|
||||
python_requires='>=3.6',
|
||||
)
|
Loading…
x
Reference in New Issue
Block a user