django-wee
A minimal Django application for creating and resolving short URLs, backed by Sqids codes and Django's cache framework.
Table of Contents
Requirements
- Python 3.10+
- Django 5.2+
Installation
pip install django-wee
Add django_wee to INSTALLED_APPS and include its URL patterns:
# settings.py
INSTALLED_APPS = [
...
"django_wee",
]
# urls.py
from django.urls import include, path
urlpatterns = [
...
path("s/", include("django_wee.urls")),
]
Then run the migrations:
python manage.py migrate
ASGI / async projects
If your project runs under an ASGI server, use the async URL module instead:
(path("s/", include("django_wee.urls_async")),)
Configuration
No mandatory configuration is required. See Settings reference for optional tunables.
Usage
Creating short URLs
Use the provided shortcut functions anywhere in your code:
from django.urls import reverse
from django_wee.shortcuts import create_short_url
short_url = create_short_url("https://example.com/a/very/long/url")
short_path = reverse("django_wee:redirect", args=[short_url.code])
# → "/s/aBcD1234/"
Pass an optional timezone-aware expiration datetime to create a URL that stops resolving after the specified time:
from datetime import timedelta
from django.urls import reverse
from django.utils import timezone
from django_wee.shortcuts import create_short_url
expiration = timezone.now() + timedelta(days=7)
short_url = create_short_url("https://example.com/a/very/long/url", expiration=expiration)
short_path = reverse("django_wee:redirect", args=[short_url.code])
If no expiration is provided, the WEE_DEFAULT_TTL setting is used as a fallback. If that setting is also absent, the short URL does not expire.
Alternatively, pass a ttl (time-to-live) to compute the expiration as a
offset from now. An int or float is interpreted as seconds, and a
timedelta is used directly. expiration and ttl are mutually exclusive:
from datetime import timedelta
from django.urls import reverse
from django_wee.shortcuts import create_short_url
short_url = create_short_url("https://example.com/a/very/long/url", ttl=timedelta(days=7))
short_path = reverse("django_wee:redirect", args=[short_url.code])
An async variant is also available:
from datetime import timedelta
from django.urls import reverse
from django.utils import timezone
from django_wee.shortcuts import acreate_short_url
expiration = timezone.now() + timedelta(days=7)
short_url = await acreate_short_url("https://example.com/a/very/long/url", expiration=expiration)
short_path = reverse("django_wee:redirect", args=[short_url.code])
Both functions validate the URL, persist a ShortUrl record, populate the cache, and return the persisted ShortUrl instance.
URLs without a scheme are normalized to https:// before validation and persistence. For example, create_short_url("example.com") stores https://example.com. Protocol-relative URLs (//example.com) receive an https: prefix, and URLs that already include a scheme are left unchanged.
Resolving short URLs
Requests to GET /s/<code>/ are handled automatically by the redirect view. The view checks the cache first and falls back to the database. Expired short URLs are not resolved. The response type (301 or 302) is controlled by the WEE_PERMANENT_REDIRECT setting.
Deleting expired URLs
The delete_expired_short_urls management command removes expired short URLs from the database:
python manage.py delete_expired_short_urls
By default, every short URL whose expires_at timestamp is in the past is deleted. Use --older-than to restrict the deletion to URLs that have been expired for at least the given duration:
python manage.py delete_expired_short_urls --older-than "7 days"
The duration accepts any format supported by Django's parse_duration (e.g. "7 days", "12 hours", "PT30M").
Use --dry-run to preview how many URLs would be deleted without performing the deletion:
python manage.py delete_expired_short_urls --older-than "7 days" --dry-run
Settings reference
| Setting | Default | Description |
|---|---|---|
WEE_CACHE_ALIAS |
"default" |
Cache alias (from CACHES) used to store short-URL mappings. |
WEE_CACHE_TIMEOUT |
3600 |
Cache TTL in seconds. |
WEE_CACHE_PREFIX |
"WEE" |
Prefix used for short-URL cache keys. Keys use the prefix:code format. |
WEE_MIN_LEN |
8 |
Minimum length of the generated Sqids code. |
WEE_ALPHABET |
Sqids default | Character set used to generate codes. |
WEE_DEFAULT_TTL |
None |
Project-wide default TTL used when no explicit expiration is given. Accepts int or float (seconds), timedelta or None (no expiration). |
WEE_PERMANENT_REDIRECT |
True |
If True, the redirect view returns HTTP 301; otherwise HTTP 302. |
License
django-wee is distributed under the terms of the MIT license.
Metadata
Release files for django-wee 0.2.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| django_wee-0.2.3.tar.gz | 20.7 kB | With provenance | Details for django_wee-0.2.3.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| django_wee-0.2.3-py3-none-any.whl | Python 3 | none | any | Details for django_wee-0.2.3-py3-none-any.whl |
Total release size: 40.6 kB
Release files / django_wee-0.2.3.tar.gz
| Download URL | django_wee-0.2.3.tar.gz |
|---|---|
| Size | 20.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
aa236a017dc9a1138ad04be98941a298a273ba1a8c3c3daaf4325a22632d3a04
|
|
BLAKE2b-256 checksum How to use checksums |
3bc1878fcdfa24ea75f02c5841e1c362bf46d6133f471cf76b1d432a77815648
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 26, 2026.
Transparency logRelease files / django_wee-0.2.3-py3-none-any.whl
| Download URL | django_wee-0.2.3-py3-none-any.whl |
|---|---|
| Size | 19.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
258ff0f0e753c44f4270bec684aee6ce498381ce2a09ffc062c60698c8d76cea
|
|
BLAKE2b-256 checksum How to use checksums |
fe91393dcfe5c125d15c58a5d7913360021ec124073223e541bed6464ec5f131
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 26, 2026.
Transparency logSecurity for django-wee 0.2.3
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for django_wee-0.2.3.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 26, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.