# Django Clickify
[](https://pypi.org/project/django-clickify/)
[](https://opensource.org/licenses/MIT)
A simple Django app to track clicks on any link (e.g., affiliate links, outbound links, file downloads) with rate limiting, IP filtering, and geolocation.
## Features
- **Click Tracking**: Logs every click on a tracked link, including IP address, user agent, and timestamp.
- **Geolocation**: Automatically enriches click logs with the country and city of the request's IP address via a web API.
- **Rate Limiting**: Prevents abuse by limiting the number of clicks per IP address in a given timeframe (can be disabled).
- **IP Filtering**: Easily configure allowlists and blocklists for IP addresses.
- **Django Admin Integration**: Create and manage your tracked links directly in the Django admin.
- **Flexible Usage**: Provides both a simple template tag for traditional Django templates and a DRF API view for headless/JavaScript applications.
## Installation
1. Install the package from PyPI:
```bash
pip install django-clickify
```
2. Add `'clickify'` to your `INSTALLED_APPS` in `settings.py`:
```python
INSTALLED_APPS = [
# ...
'clickify',
]
```
3. Run migrations to create the necessary database models:
```bash
python manage.py migrate
```
4. **For API support (Optional)**: If you plan to use the DRF view, you can either install with the `[drf]` or `[full]` extra (see step 5) or manually install `djangorestframework` and add it to your `INSTALLED_APPS`.
```bash
pip install djangorestframework
```
```python
INSTALLED_APPS = [
# ...
'rest_framework',
'clickify',
]
```
5. **Optional Dependencies**: This package supports optional features that require additional dependencies:
```bash
# For rate limiting support only
pip install django-clickify[ratelimit]
# For DRF API support only
pip install django-clickify[drf]
# For all optional features
pip install django-clickify[full]
```
Alternatively, you can install the base package and add dependencies manually as described in the sections above.
## Configuration
You can customize the behavior of `django-clickify` by adding the following settings to your project's `settings.py` file.
### General Settings
- `CLICKIFY_GEOLOCATION`: A boolean to enable or disable geolocation. Defaults to `True`.
- `CLICKIFY_ENABLE_RATELIMIT`: A boolean to enable or disable rate limiting. Defaults to `True`. Set to `False` to disable all rate limiting.
- `CLICKIFY_RATE_LIMIT`: The rate limit for clicks when rate limiting is enabled. Defaults to `'5/m'` (5 requests per minute).
- `CLICKIFY_RATELIMIT_MESSAGE`: Customize the message displayed when the rate limit is exceeded. This is used for both template-based views (via the Django messages framework) and the DRF API. Defaults to `"You have made too many requests. Please try again later"`.
- `CLICKIFY_IP_ALLOWLIST`: A list of IP addresses that are always allowed. Defaults to `[]`.
- `CLICKIFY_IP_BLOCKLIST`: A list of IP addresses that are always blocked. Defaults to `[]`.
- `CLICKIFY_IP_HEADERS`: A list of HTTP headers to check for the real client IP address (useful when behind proxies or load balancers). Defaults to:
```python
[
"HTTP_X_FORWARDED_FOR",
"HTTP_X_REAL_IP",
"HTTP_X_FORWARDED",
"HTTP_X_CLUSTER_CLIENT_IP",
"HTTP_FORWARDED_FOR",
"HTTP_FORWARDED",
"REMOTE_ADDR",
]
```
### Rate Limiting Configuration
Rate limiting is enabled by default but can be completely disabled:
```python
# settings.py
# Disable rate limiting entirely
CLICKIFY_ENABLE_RATELIMIT = False
# Or configure rate limiting (when enabled)
CLICKIFY_ENABLE_RATELIMIT = True
CLICKIFY_RATE_LIMIT = '10/h' # 10 requests per hour
CLICKIFY_RATELIMIT_MESSAGE = "Too many clicks! Please wait before trying again."
```
**Note**: When rate limiting is enabled, you can either install with the `[ratelimit]` or `[full]` extra, or manually install `django-ratelimit`:
```bash
pip install django-ratelimit
```
### IP Detection Configuration
For applications behind proxies or load balancers, configure the IP headers to check:
```python
# settings.py
# Custom IP headers (checked in order)
CLICKIFY_IP_HEADERS = [
"HTTP_CF_CONNECTING_IP", # Cloudflare
"HTTP_X_FORWARDED_FOR", # Standard proxy header
"HTTP_X_REAL_IP", # Nginx
"REMOTE_ADDR", # Fallback
]
```
### Middleware (for IP Filtering)
To enable the IP allowlist and blocklist feature, add the `IPFilterMiddleware` to your `settings.py`.
```python
MIDDLEWARE = [
# ...
'clickify.middleware.IPFilterMiddleware',
# ...
]
```
## Usage
First, create a `TrackedLink` in your Django Admin under the "Clickify" section. This target can be any URL you want to track.
- **Name:** `Monthly Report PDF`
- **Slug:** `monthly-report-pdf` (this will be auto-populated from the name)
- **Target Url:** `https://your-s3-bucket.s3.amazonaws.com/reports/monthly-summary.pdf`
Once a `TrackedLink` is created, you can use it in one of two ways.
### Option 1: Template-Based Usage
This is the standard way to use the app in traditional Django projects.
#### Step 1: Include Clickify URLs
In your project's `urls.py`, include the `clickify` URL patterns. You can choose any path you like for the tracking URLs.
```python
# your_project/urls.py
from django.urls import path, include
urlpatterns = [
# ... your other urls
path('track/', include('clickify.urls', namespace='clickify')),
]
```
#### Step 2: Use the Template Tag
In your Django template, use the `track_url` template tag to generate the tracking link.
```html
<!-- your_app/templates/my_template.html -->
{% load clickify_tags %}
<a href="{% track_url 'monthly-report-pdf' %}"> Get Monthly Summary </a>
```
When a user exceeds the rate limit (if enabled), `django-clickify` uses the [Django messages framework](https://docs.djangoproject.com/en/stable/ref/contrib/messages/) to display an error. The user is then redirected back to the page they came from.
To display the message in your templates, make sure you have configured the messages framework and included code to render the messages, like this:
```html
{% if messages %}
<ul class="messages">
{% for message in messages %}
<li class="{{ message.tags }}">{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
```
### Option 2: API Usage (for Headless/JS Frameworks)
If you are using a JavaScript frontend (like React, Vue, etc.) or need a programmatic way to track a click, you can use the DRF API endpoint.
#### Step 1: Include Clickify DRF URLs
In your project's `urls.py`, include the `clickify.drf_urls` patterns. You can choose any path you like for the API endpoint.
```python
# your_project/urls.py
from django.urls import path, include
urlpatterns = [
# ... your other urls
path('api/track/', include('clickify.drf_urls', namespace='clickify-api')),
]
```
#### Step 2: Make the API Request
From your frontend, make a `POST` request to the API endpoint using the slug of your `TrackedLink`.
**Endpoint**: `POST /api/track/<slug>/`
**Example using JavaScript `fetch`:**
```javascript
fetch("/api/track/monthly-report-pdf/", {
method: "POST",
headers: {
// Include CSRF token if necessary for your setup
"X-CSRFToken": "YourCsrfTokenHere",
},
})
.then((response) => response.json())
.then((data) => {
if (data.target_url) {
console.log("Click tracked. Redirecting to:", data.target_url);
// Redirect the user to the URL
window.location.href = data.target_url;
} else {
console.error("Failed to track click:", data);
}
})
.catch((error) => {
console.error("Error:", error);
});
```
**Successful Response (`200 OK`):**
```json
{
"message": "Click tracked successfully",
"target_url": "https://your-s3-bucket.s3.amazonaws.com/reports/monthly-summary.pdf"
}
```
**Rate Limited Response (`429 Too Many Requests`):**
```json
{
"error": "You have made too many requests. Please try again later"
}
```
#### API-Specific Configuration
- **`CLICKIFY_PERMISSION_CLASSES`**: Control who can access the tracking API endpoint. This should be a list of DRF permission classes. Defaults to `[AllowAny]`.
```python
# settings.py
CLICKIFY_PERMISSION_CLASSES = ["rest_framework.permissions.IsAuthenticated"]
```
- **Improving API Error Responses**: To provide clearer, more consistent error messages for the API (e.g., for rate limiting or authentication errors), you can use the custom exception handling logic included with `django-clickify`.
- **If you DO NOT have a custom exception handler:**
In your `settings.py`, you can use the handler provided by `django-clickify` directly:
```python
# settings.py
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'clickify.drf_exception_handler.custom_exception_handler'
}
```
- **If you ALREADY have a custom exception handler:**
You can easily integrate `django-clickify`'s logic into your existing handler.
```python
# your_project/your_app/exception_handler.py
from rest_framework.views import exception_handler
from clickify.drf_exception_handler import handle_clickify_exceptions # <-- Import
def your_custom_handler(exc, context):
# First, check for clickify-specific exceptions
response = handle_clickify_exceptions(exc)
if response is not None:
return response
# Continue with your existing custom error handling logic
# ...
# Fall back to the default DRF handler
return exception_handler(exc, context)
```
## How It Works
1. A user clicks a tracked link (`/track/monthly-report-pdf/`) or a `POST` request is sent to the API.
2. The view or API view records the click event in the database, associating it with the correct `TrackedLink`.
3. If rate limiting is enabled and the user has exceeded the limit, they receive an error message and are redirected back (for template views) or receive a JSON error response (for API views).
4. For successful requests, the standard view issues a `302 Redirect` to the `target_url`. The API view returns a JSON response containing the `target_url`.
5. The user's browser is redirected to the final destination.
This approach is powerful because if you ever need to change the link's destination, you only need to update the `Target Url` in the Django Admin. All your tracked links and API calls will continue to work correctly.
Raw data
{
"_id": null,
"home_page": "https://github.com/romjanxr/django-clickify",
"name": "django-clickify",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.10",
"maintainer_email": null,
"keywords": "django, click, tracker, ratelimit, ipfilter, geolocation, analytics",
"author": "Romjan Ali",
"author_email": "romjanvr5@gmail.com",
"download_url": "https://files.pythonhosted.org/packages/ab/65/424bee7a72f8e508940dc46ceb84d2d231b2606c8ba92785825e78f3952c/django_clickify-0.2.0.tar.gz",
"platform": null,
"description": "# Django Clickify\n\n[](https://pypi.org/project/django-clickify/)\n[](https://opensource.org/licenses/MIT)\n\nA simple Django app to track clicks on any link (e.g., affiliate links, outbound links, file downloads) with rate limiting, IP filtering, and geolocation.\n\n## Features\n\n- **Click Tracking**: Logs every click on a tracked link, including IP address, user agent, and timestamp.\n- **Geolocation**: Automatically enriches click logs with the country and city of the request's IP address via a web API.\n- **Rate Limiting**: Prevents abuse by limiting the number of clicks per IP address in a given timeframe (can be disabled).\n- **IP Filtering**: Easily configure allowlists and blocklists for IP addresses.\n- **Django Admin Integration**: Create and manage your tracked links directly in the Django admin.\n- **Flexible Usage**: Provides both a simple template tag for traditional Django templates and a DRF API view for headless/JavaScript applications.\n\n## Installation\n\n1. Install the package from PyPI:\n\n ```bash\n pip install django-clickify\n ```\n\n2. Add `'clickify'` to your `INSTALLED_APPS` in `settings.py`:\n\n ```python\n INSTALLED_APPS = [\n # ...\n 'clickify',\n ]\n ```\n\n3. Run migrations to create the necessary database models:\n\n ```bash\n python manage.py migrate\n ```\n\n4. **For API support (Optional)**: If you plan to use the DRF view, you can either install with the `[drf]` or `[full]` extra (see step 5) or manually install `djangorestframework` and add it to your `INSTALLED_APPS`.\n\n ```bash\n pip install djangorestframework\n ```\n\n ```python\n INSTALLED_APPS = [\n # ...\n 'rest_framework',\n 'clickify',\n ]\n ```\n\n5. **Optional Dependencies**: This package supports optional features that require additional dependencies:\n\n ```bash\n # For rate limiting support only\n pip install django-clickify[ratelimit]\n\n # For DRF API support only\n pip install django-clickify[drf]\n\n # For all optional features\n pip install django-clickify[full]\n ```\n\n Alternatively, you can install the base package and add dependencies manually as described in the sections above.\n\n## Configuration\n\nYou can customize the behavior of `django-clickify` by adding the following settings to your project's `settings.py` file.\n\n### General Settings\n\n- `CLICKIFY_GEOLOCATION`: A boolean to enable or disable geolocation. Defaults to `True`.\n- `CLICKIFY_ENABLE_RATELIMIT`: A boolean to enable or disable rate limiting. Defaults to `True`. Set to `False` to disable all rate limiting.\n- `CLICKIFY_RATE_LIMIT`: The rate limit for clicks when rate limiting is enabled. Defaults to `'5/m'` (5 requests per minute).\n- `CLICKIFY_RATELIMIT_MESSAGE`: Customize the message displayed when the rate limit is exceeded. This is used for both template-based views (via the Django messages framework) and the DRF API. Defaults to `\"You have made too many requests. Please try again later\"`.\n- `CLICKIFY_IP_ALLOWLIST`: A list of IP addresses that are always allowed. Defaults to `[]`.\n- `CLICKIFY_IP_BLOCKLIST`: A list of IP addresses that are always blocked. Defaults to `[]`.\n- `CLICKIFY_IP_HEADERS`: A list of HTTP headers to check for the real client IP address (useful when behind proxies or load balancers). Defaults to:\n ```python\n [\n \"HTTP_X_FORWARDED_FOR\",\n \"HTTP_X_REAL_IP\",\n \"HTTP_X_FORWARDED\",\n \"HTTP_X_CLUSTER_CLIENT_IP\",\n \"HTTP_FORWARDED_FOR\",\n \"HTTP_FORWARDED\",\n \"REMOTE_ADDR\",\n ]\n ```\n\n### Rate Limiting Configuration\n\nRate limiting is enabled by default but can be completely disabled:\n\n```python\n# settings.py\n\n# Disable rate limiting entirely\nCLICKIFY_ENABLE_RATELIMIT = False\n\n# Or configure rate limiting (when enabled)\nCLICKIFY_ENABLE_RATELIMIT = True\nCLICKIFY_RATE_LIMIT = '10/h' # 10 requests per hour\nCLICKIFY_RATELIMIT_MESSAGE = \"Too many clicks! Please wait before trying again.\"\n```\n\n**Note**: When rate limiting is enabled, you can either install with the `[ratelimit]` or `[full]` extra, or manually install `django-ratelimit`:\n\n```bash\npip install django-ratelimit\n```\n\n### IP Detection Configuration\n\nFor applications behind proxies or load balancers, configure the IP headers to check:\n\n```python\n# settings.py\n\n# Custom IP headers (checked in order)\nCLICKIFY_IP_HEADERS = [\n \"HTTP_CF_CONNECTING_IP\", # Cloudflare\n \"HTTP_X_FORWARDED_FOR\", # Standard proxy header\n \"HTTP_X_REAL_IP\", # Nginx\n \"REMOTE_ADDR\", # Fallback\n]\n```\n\n### Middleware (for IP Filtering)\n\nTo enable the IP allowlist and blocklist feature, add the `IPFilterMiddleware` to your `settings.py`.\n\n```python\nMIDDLEWARE = [\n # ...\n 'clickify.middleware.IPFilterMiddleware',\n # ...\n]\n```\n\n## Usage\n\nFirst, create a `TrackedLink` in your Django Admin under the \"Clickify\" section. This target can be any URL you want to track.\n\n- **Name:** `Monthly Report PDF`\n- **Slug:** `monthly-report-pdf` (this will be auto-populated from the name)\n- **Target Url:** `https://your-s3-bucket.s3.amazonaws.com/reports/monthly-summary.pdf`\n\nOnce a `TrackedLink` is created, you can use it in one of two ways.\n\n### Option 1: Template-Based Usage\n\nThis is the standard way to use the app in traditional Django projects.\n\n#### Step 1: Include Clickify URLs\n\nIn your project's `urls.py`, include the `clickify` URL patterns. You can choose any path you like for the tracking URLs.\n\n```python\n# your_project/urls.py\nfrom django.urls import path, include\n\nurlpatterns = [\n # ... your other urls\n path('track/', include('clickify.urls', namespace='clickify')),\n]\n```\n\n#### Step 2: Use the Template Tag\n\nIn your Django template, use the `track_url` template tag to generate the tracking link.\n\n```html\n<!-- your_app/templates/my_template.html -->\n{% load clickify_tags %}\n\n<a href=\"{% track_url 'monthly-report-pdf' %}\"> Get Monthly Summary </a>\n```\n\nWhen a user exceeds the rate limit (if enabled), `django-clickify` uses the [Django messages framework](https://docs.djangoproject.com/en/stable/ref/contrib/messages/) to display an error. The user is then redirected back to the page they came from.\n\nTo display the message in your templates, make sure you have configured the messages framework and included code to render the messages, like this:\n\n```html\n{% if messages %}\n<ul class=\"messages\">\n {% for message in messages %}\n <li class=\"{{ message.tags }}\">{{ message }}</li>\n {% endfor %}\n</ul>\n{% endif %}\n```\n\n### Option 2: API Usage (for Headless/JS Frameworks)\n\nIf you are using a JavaScript frontend (like React, Vue, etc.) or need a programmatic way to track a click, you can use the DRF API endpoint.\n\n#### Step 1: Include Clickify DRF URLs\n\nIn your project's `urls.py`, include the `clickify.drf_urls` patterns. You can choose any path you like for the API endpoint.\n\n```python\n# your_project/urls.py\nfrom django.urls import path, include\n\nurlpatterns = [\n # ... your other urls\n path('api/track/', include('clickify.drf_urls', namespace='clickify-api')),\n]\n```\n\n#### Step 2: Make the API Request\n\nFrom your frontend, make a `POST` request to the API endpoint using the slug of your `TrackedLink`.\n\n**Endpoint**: `POST /api/track/<slug>/`\n\n**Example using JavaScript `fetch`:**\n\n```javascript\nfetch(\"/api/track/monthly-report-pdf/\", {\n method: \"POST\",\n headers: {\n // Include CSRF token if necessary for your setup\n \"X-CSRFToken\": \"YourCsrfTokenHere\",\n },\n})\n .then((response) => response.json())\n .then((data) => {\n if (data.target_url) {\n console.log(\"Click tracked. Redirecting to:\", data.target_url);\n // Redirect the user to the URL\n window.location.href = data.target_url;\n } else {\n console.error(\"Failed to track click:\", data);\n }\n })\n .catch((error) => {\n console.error(\"Error:\", error);\n });\n```\n\n**Successful Response (`200 OK`):**\n\n```json\n{\n \"message\": \"Click tracked successfully\",\n \"target_url\": \"https://your-s3-bucket.s3.amazonaws.com/reports/monthly-summary.pdf\"\n}\n```\n\n**Rate Limited Response (`429 Too Many Requests`):**\n\n```json\n{\n \"error\": \"You have made too many requests. Please try again later\"\n}\n```\n\n#### API-Specific Configuration\n\n- **`CLICKIFY_PERMISSION_CLASSES`**: Control who can access the tracking API endpoint. This should be a list of DRF permission classes. Defaults to `[AllowAny]`.\n\n ```python\n # settings.py\n CLICKIFY_PERMISSION_CLASSES = [\"rest_framework.permissions.IsAuthenticated\"]\n ```\n\n- **Improving API Error Responses**: To provide clearer, more consistent error messages for the API (e.g., for rate limiting or authentication errors), you can use the custom exception handling logic included with `django-clickify`.\n\n - **If you DO NOT have a custom exception handler:**\n In your `settings.py`, you can use the handler provided by `django-clickify` directly:\n\n ```python\n # settings.py\n REST_FRAMEWORK = {\n 'EXCEPTION_HANDLER': 'clickify.drf_exception_handler.custom_exception_handler'\n }\n ```\n\n - **If you ALREADY have a custom exception handler:**\n You can easily integrate `django-clickify`'s logic into your existing handler.\n\n ```python\n # your_project/your_app/exception_handler.py\n from rest_framework.views import exception_handler\n from clickify.drf_exception_handler import handle_clickify_exceptions # <-- Import\n\n def your_custom_handler(exc, context):\n # First, check for clickify-specific exceptions\n response = handle_clickify_exceptions(exc)\n if response is not None:\n return response\n\n # Continue with your existing custom error handling logic\n # ...\n\n # Fall back to the default DRF handler\n return exception_handler(exc, context)\n ```\n\n## How It Works\n\n1. A user clicks a tracked link (`/track/monthly-report-pdf/`) or a `POST` request is sent to the API.\n2. The view or API view records the click event in the database, associating it with the correct `TrackedLink`.\n3. If rate limiting is enabled and the user has exceeded the limit, they receive an error message and are redirected back (for template views) or receive a JSON error response (for API views).\n4. For successful requests, the standard view issues a `302 Redirect` to the `target_url`. The API view returns a JSON response containing the `target_url`.\n5. The user's browser is redirected to the final destination.\n\nThis approach is powerful because if you ever need to change the link's destination, you only need to update the `Target Url` in the Django Admin. All your tracked links and API calls will continue to work correctly.\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "A Django app to track link clicks with rate limiting, IP filtering, and geolocation.",
"version": "0.2.0",
"project_urls": {
"Documentation": "https://github.com/romjanxr/django-clickify#readme",
"Homepage": "https://github.com/romjanxr/django-clickify",
"Repository": "https://github.com/romjanxr/django-clickify"
},
"split_keywords": [
"django",
" click",
" tracker",
" ratelimit",
" ipfilter",
" geolocation",
" analytics"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "48c62e13c59b4d65ed92dbe99b45b8fa18a0effe1ea24be76d28d82c93483604",
"md5": "61bae512ee1ff0cddac56909e01071b4",
"sha256": "44fa0d56f18c00ad28cc67d5e5da201c421d2922d9965063a6c9ad8d539ef7ce"
},
"downloads": -1,
"filename": "django_clickify-0.2.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "61bae512ee1ff0cddac56909e01071b4",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.10",
"size": 15457,
"upload_time": "2025-08-31T10:00:33",
"upload_time_iso_8601": "2025-08-31T10:00:33.541915Z",
"url": "https://files.pythonhosted.org/packages/48/c6/2e13c59b4d65ed92dbe99b45b8fa18a0effe1ea24be76d28d82c93483604/django_clickify-0.2.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "ab65424bee7a72f8e508940dc46ceb84d2d231b2606c8ba92785825e78f3952c",
"md5": "0b1af5d66852f8d263f99082e9405fbe",
"sha256": "1e0f5524ddb0ff3383981117f28a37d65ff48ef720554a61b3c3e6bb8a69a6a4"
},
"downloads": -1,
"filename": "django_clickify-0.2.0.tar.gz",
"has_sig": false,
"md5_digest": "0b1af5d66852f8d263f99082e9405fbe",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.10",
"size": 14546,
"upload_time": "2025-08-31T10:00:35",
"upload_time_iso_8601": "2025-08-31T10:00:35.368160Z",
"url": "https://files.pythonhosted.org/packages/ab/65/424bee7a72f8e508940dc46ceb84d2d231b2606c8ba92785825e78f3952c/django_clickify-0.2.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-08-31 10:00:35",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "romjanxr",
"github_project": "django-clickify",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"lcname": "django-clickify"
}