Full-featured dynamic QR code package for Django — 14 types, analytics, access control, REST API
Project description
django-dynamic-qr
A full-featured, self-hosted dynamic QR code package for Django — a QRFY-equivalent you own completely.
14 QR types · short-URL redirects · per-scan analytics · password/schedule/geo access control · bulk CSV/ZIP · REST API · Django admin with live previews.
Features
- 14 QR types: URL, vCard, vCard+ (rich landing page), PDF, Restaurant Menu, Social Links hub, WiFi, Email, SMS, WhatsApp, Image Gallery, Plain Text, Link List, App Store (smart iOS/Android redirect)
- Dynamic short URLs — edit destination after printing, no reprint needed
- Design engine — custom colors, logo overlay, frame styles, 4 error-correction levels
- Export formats — PNG, SVG, PDF, EPS
- Analytics — scan count, device/OS/browser, country/city geolocation, daily trend, CSV/XLSX export
- Access control — password protection, daily scan limits, scheduled activation/expiry, country allow-list
- Bulk operations — CSV import, ZIP export of multiple QR images
- REST API — full DRF viewsets with token auth (optional)
- Django admin — inline scan logs, live QR thumbnail preview, bulk actions
- Async-ready — optional Celery task for non-blocking scan logging
Installation
pip install django-dynamic-qr[api]
# or, from local source:
pip install -e /path/to/django-dynamic-qr
1. Add to INSTALLED_APPS
INSTALLED_APPS = [
...
"django_dynamic_qr",
"rest_framework", # only if using the REST API
]
2. Include URLs
# project/urls.py
from django.urls import path, include
urlpatterns = [
...
path("", include("django_dynamic_qr.urls")),
]
This mounts:
/qr/<slug>/— the redirect / landing-page endpoint (this is what your QR images encode)/qr/<slug>/export/— download the QR image/api/qr/...— REST API (if DRF installed)
3. Configure settings (all optional — sane defaults provided)
DJANGO_DYNAMIC_QR = {
"SHORT_URL_BASE": "https://yourdomain.com", # IMPORTANT: set this in production
"DEFAULT_ERROR_CORRECTION": "H",
"DEFAULT_BOX_SIZE": 10,
"DEFAULT_BORDER": 4,
"LOGO_MAX_RATIO": 0.25,
"SCAN_LOG_ASYNC": False, # True if Celery is configured
"GEO_BACKEND": "ip-api", # "ip-api" | "maxmind" | None
"MAXMIND_DB_PATH": None,
"ENABLE_REST_API": True,
"UPLOAD_TO": "qr_codes/",
"LOGO_UPLOAD_TO": "qr_logos/",
}
4. Migrate
python manage.py migrate django_dynamic_qr
5. Make sure MEDIA_URL / MEDIA_ROOT are configured (for logo uploads) and your urls.py serves media in development.
Usage
Create a QR code programmatically
from django_dynamic_qr.models import QRCode
qr = QRCode.objects.create(
user=request.user,
name="My Website",
qr_type="url",
type_data={"url": "https://example.com"},
fg_color="#111111",
bg_color="#FFFFFF",
)
print(qr.get_redirect_url()) # https://yourdomain.com/qr/AbC123Xy/
Export the QR image
from django_dynamic_qr.engine.exporter import export_qr_code
png_bytes = export_qr_code(qr, fmt="png")
svg_bytes = export_qr_code(qr, fmt="svg")
Or via the built-in view: GET /qr/<slug>/export/?fmt=svg
Update the destination — no reprint needed
qr.type_data = {"url": "https://example.com/new-page"}
qr.save()
# The printed QR code, slug, and image stay identical.
# Anyone scanning it is now redirected to the new URL.
Add access control
from django.utils import timezone
import datetime
qr.password = "secret123"
qr.scan_limit_day = 500
qr.active_from = timezone.now()
qr.active_until = timezone.now() + datetime.timedelta(days=30)
qr.allowed_countries = ["IN", "US", "GB"]
qr.save()
Bulk import from CSV
CSV format:
name,qr_type,data
My Website,url,"{""url"": ""https://example.com""}"
Office WiFi,wifi,"{""ssid"": ""OfficeNet"", ""password"": ""pass123"", ""security"": ""WPA""}"
from django_dynamic_qr.bulk import import_from_csv
created, errors = import_from_csv(csv_file, user=request.user)
Management command
python manage.py generate_qr <slug> --fmt png --output qr.png
REST API (optional, requires djangorestframework)
GET /api/qr/ list QR codes
POST /api/qr/ create
GET /api/qr/<id>/ retrieve
PATCH /api/qr/<id>/ update destination (dynamic!)
DELETE /api/qr/<id>/
GET /api/qr/<id>/export/?fmt=png download image
GET /api/qr/<id>/analytics/ scan stats summary
GET /api/qr/<id>/analytics/export/ download scan logs CSV/XLSX
POST /api/qr/bulk-import/ CSV bulk create
GET /api/qr/bulk-export/?fmt=svg ZIP export
Type payload reference
| Type | type_data keys |
|---|---|
url |
url |
vcard |
first_name, last_name, phone, email, company, title, address, website |
vcard_plus |
same as vcard + photo_url, bio, socials: {platform: url} |
pdf |
pdf_url, title |
menu |
restaurant_name, logo_url, categories: [{name, items: [{name, description, price, image_url}]}] |
social |
title, bio, avatar_url, links: [{platform, url, label}] |
wifi |
ssid, password, security, hidden |
email |
to, subject, body |
sms |
phone, message |
whatsapp |
phone, message |
image |
title, images: [{url, caption}] |
text |
text |
link_list |
title, description, links: [{label, url, icon}] |
app_store |
ios_url, android_url, fallback_url |
Extending with a custom QR type
# myapp/qr_types.py
from django_dynamic_qr.types.base import BaseQRType
from django_dynamic_qr.types import registry
class CalendarEventType(BaseQRType):
type_id = "calendar_event"
is_static = True
def get_destination(self):
d = self.data
return f"BEGIN:VEVENT\nSUMMARY:{d['title']}\nDTSTART:{d['start']}\nEND:VEVENT"
registry["calendar_event"] = CalendarEventType
Import this module in your app's ready() to register it at startup.
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file django_dynamic_qr-1.0.0.tar.gz.
File metadata
- Download URL: django_dynamic_qr-1.0.0.tar.gz
- Upload date:
- Size: 32.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d797c2bb9643aa0550a543e7454ac64b55e3530523ce2825c7e9a6923b933d2f
|
|
| MD5 |
89f42f2d670c640c7d3422ea1113c60b
|
|
| BLAKE2b-256 |
e6ac67ff595d7bc628559988ec16ab58d2c39ff9f8447139999b41290d6e83ef
|
Provenance
The following attestation bundles were made for django_dynamic_qr-1.0.0.tar.gz:
Publisher:
release.yml on rahul-baberwal/django-dynamic-qr
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
django_dynamic_qr-1.0.0.tar.gz -
Subject digest:
d797c2bb9643aa0550a543e7454ac64b55e3530523ce2825c7e9a6923b933d2f - Sigstore transparency entry: 2018559366
- Sigstore integration time:
-
Permalink:
rahul-baberwal/django-dynamic-qr@6e2af51d95b0ddbd25b9f8b27880e375230e976b -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/rahul-baberwal
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@6e2af51d95b0ddbd25b9f8b27880e375230e976b -
Trigger Event:
push
-
Statement type:
File details
Details for the file django_dynamic_qr-1.0.0-py3-none-any.whl.
File metadata
- Download URL: django_dynamic_qr-1.0.0-py3-none-any.whl
- Upload date:
- Size: 48.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
89bf69b618064f58be1dbfcec504c464e75a97a1ab04cd3e5693d3fb43947dbd
|
|
| MD5 |
480e3267b96ff200c791668631a41764
|
|
| BLAKE2b-256 |
67e4e1a36b6df6fa7527e1a740de289d7b07593299a4e31bc98424bfcf7c81d8
|
Provenance
The following attestation bundles were made for django_dynamic_qr-1.0.0-py3-none-any.whl:
Publisher:
release.yml on rahul-baberwal/django-dynamic-qr
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
django_dynamic_qr-1.0.0-py3-none-any.whl -
Subject digest:
89bf69b618064f58be1dbfcec504c464e75a97a1ab04cd3e5693d3fb43947dbd - Sigstore transparency entry: 2018559527
- Sigstore integration time:
-
Permalink:
rahul-baberwal/django-dynamic-qr@6e2af51d95b0ddbd25b9f8b27880e375230e976b -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/rahul-baberwal
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@6e2af51d95b0ddbd25b9f8b27880e375230e976b -
Trigger Event:
push
-
Statement type: