Skip to main content

Ipregistry Django

License CI PyPI

The official Django integration for the Ipregistry IP geolocation and threat data API, built on top of ipregistry-python.

def view(request):
    if request.ipregistry and request.ipregistry.location.country.code == "FR":
        ...

Features

  • request.ipregistry — geolocation and threat data anywhere a request is in scope, queried at most once per request and only when actually accessed.
  • Middleware and view decorators to block countries (451) or threats such as proxies, Tor and VPNs (403), site-wide or per-view.
  • Django caching — repeated visits from the same IP cost a single credit, backed by any configured cache (Redis, Valkey, Memcached, ...).
  • GDPR helper — is_eu(request) to decide when to show consent banners.
  • Safe by default — fails open on API errors, never sends private IPs, never logs full IP addresses, supports async views and ASGI.
  • Testable — fake_ipregistry() stubs lookups with zero HTTP and records assertions.
  • manage.py ipregistry_lookup command and Django system checks for misconfiguration.

Requirements

  • Django 5.2+ (5.2 LTS and 6.0 are tested)
  • Python 3.10+

Installation

pip install ipregistry-django

Set your API key (get one here), preferably via the environment:

export IPREGISTRY_API_KEY=YOUR_API_KEY

Then enable the app and middleware in settings.py:

INSTALLED_APPS = [
    # ...
    "ipregistry_django",
]

MIDDLEWARE = [
    # ...
    "ipregistry_django.middleware.IpregistryMiddleware",
]

That's it. Every view (and everything downstream of the middleware) can now read request.ipregistry.

Usage

Request data

request.ipregistry is a lazy IpInfo object. The API is only queried when the attribute is first accessed, at most once per request:

def checkout(request):
    info = request.ipregistry
    if info:
        currency = info.currency.code
        country = info.location.country.name
        is_vpn = info.security.is_vpn

On lookup failure (or when the client IP is private and no DEVELOPMENT_IP is set), request.ipregistry evaluates to None-like falsy and the exception is available at request.ipregistry_error. Prefer truthiness checks (if request.ipregistry:) over is None because of the lazy wrapper — or call get_ipregistry(request) directly:

from ipregistry_django import get_ipregistry

info = get_ipregistry(request)  # plain IpInfo or None, memoized, never raises

Async views

from ipregistry_django import aget_ipregistry

async def view(request):
    info = await aget_ipregistry(request)

Direct lookups

For lookups outside the request cycle (Celery tasks, shell, ...), use the module-level functions — they share the configured client and cache, and raise on failure:

import ipregistry_django

response = ipregistry_django.lookup_ip("54.85.132.205")
print(response.data.location.country.name)

response = ipregistry_django.batch_lookup_ips(["8.8.8.8", "1.1.1.1"])
response = ipregistry_django.lookup_asn(42)
response = ipregistry_django.parse_user_agent("Mozilla/5.0 ...")
response = ipregistry_django.origin_lookup_ip()

Blocking countries

Site-wide, with middleware:

MIDDLEWARE = [
    # ...
    "ipregistry_django.middleware.BlockCountriesMiddleware",
]

IPREGISTRY = {
    "BLOCK_COUNTRIES": ["KP", "SY"],  # or ALLOW_COUNTRIES for an allow-list
}

Per-view, with decorators:

from ipregistry_django.decorators import allow_countries, block_countries

@block_countries("KP", "SY")
def view(request): ...

@allow_countries("DE", "FR")
def eu_only_view(request): ...

Blocked visitors receive a 451 Unavailable For Legal Reasons response. Visitors whose country cannot be determined pass through (fail-open).

Blocking threats

MIDDLEWARE = [
    # ...
    "ipregistry_django.middleware.BlockThreatsMiddleware",
]

IPREGISTRY = {
    "BLOCK_THREATS": ["proxy", "tor", "vpn"],  # optional extra signals
}
from ipregistry_django.decorators import block_threats

@block_threats("proxy", "tor")
def view(request): ...

IPs flagged as threats, attackers or abusers are always blocked. Anonymizing services (anonymous, proxy, relay, tor, vpn) are opt-in signals. Blocked requests raise PermissionDenied — just like permission_required — so your project's handler403 and custom 403 template render the response.

For class-based views, use Django's method_decorator:

from django.utils.decorators import method_decorator

@method_decorator(block_threats("vpn"), name="dispatch")
class CheckoutView(View): ...

GDPR / EU detection

from ipregistry_django import is_bot, is_eu, is_threat

if is_eu(request, assume_eu=True):  # assume EU when unknown: show the banner
    ...

if is_threat(request, proxy=True, tor=True):
    ...

if is_bot(request):  # User-Agent heuristic, no API call, no credits
    ...

Templates

Add the context processor to render lookup data directly in templates:

TEMPLATES = [{
    "OPTIONS": {
        "context_processors": [
            # ...
            "ipregistry_django.context_processors.ipregistry",
        ],
    },
}]
{% if ipregistry %}Hello, visitor from {{ ipregistry.location.country.name }}!{% endif %}

Configuration

All settings live in a single optional IPREGISTRY dict. Defaults shown:

IPREGISTRY = {
    "ALLOW_COUNTRIES": [],        # allow-list for BlockCountriesMiddleware
    "API_KEY": "",                # falls back to the IPREGISTRY_API_KEY env var
    "BASE_URL": None,             # None: default endpoint, "eu": EU endpoint, else verbatim
    "BLOCK_COUNTRIES": [],        # deny-list for BlockCountriesMiddleware
    "BLOCK_THREATS": [],          # extra signals for BlockThreatsMiddleware
    "CACHE": True,                # cache lookups with Django's cache framework
    "CACHE_ALIAS": "default",     # which CACHES alias to use
    "CACHE_KEY_PREFIX": "ipregistry",
    "CACHE_TTL": 600,             # seconds
    "CLIENT_IP_HEADER": None,     # e.g. "X-Forwarded-For" or "CF-Connecting-IP"
    "DEVELOPMENT_IP": None,       # public IP to use when the client IP is private
    "EXEMPT_PATHS": None,         # blocker-exempt path prefixes; None: STATIC_URL/MEDIA_URL
    "FAIL_OPEN": True,            # False: blockers answer 503 on lookup failure
    "FIELDS": None,               # e.g. "ip,location,security" to lower payload size
    "HOSTNAME": False,            # resolve reverse DNS hostnames (extra credits)
    "RETRIES": 1,
    "RETRY_INTERVAL": 1.0,
    "RETRY_ON_SERVER_ERROR": True,
    "RETRY_ON_TOO_MANY_REQUESTS": False,
    "TIMEOUT": 5,                 # seconds
}

API_KEY and BASE_URL also read the IPREGISTRY_API_KEY and IPREGISTRY_BASE_URL environment variables when not set explicitly.

Client IP and reverse proxies

By default the client IP comes from REMOTE_ADDR. Behind a reverse proxy or CDN, set CLIENT_IP_HEADER to the header your proxy controls — never one clients can spoof:

IPREGISTRY = {"CLIENT_IP_HEADER": "CF-Connecting-IP"}  # Cloudflare
IPREGISTRY = {"CLIENT_IP_HEADER": "X-Forwarded-For"}   # nginx with a correct set_real_ip

The first public address found wins. Private and reserved addresses are never sent to the API; during local development, set DEVELOPMENT_IP to a fixed public IP so lookups work from localhost.

Caching

Lookups are cached in the CACHE_ALIAS Django cache for CACHE_TTL seconds (keys are hashed, so any backend works, including Memcached). On top of that, results are memoized per request, so accessing request.ipregistry repeatedly costs nothing.

EU endpoint

To ensure requests are handled by Ipregistry nodes hosted in the EU only:

IPREGISTRY = {"BASE_URL": "eu"}

Errors

request.ipregistry, get_ipregistry(), guards, middleware and decorators never raise — they fail open and log to the "ipregistry" logger (with anonymized IPs). Set "FAIL_OPEN": False to have the blocking middleware and decorators answer 503 when a lookup fails. The direct lookup functions raise ipregistry.ApiError / ipregistry.ClientError.

Run python manage.py check to surface configuration problems (missing API key, unknown setting keys, invalid country codes or threat signals, unknown cache alias).

Testing your app

fake_ipregistry swaps the shared client for a stub — no HTTP, private test-client IPs allowed:

from ipregistry_django.testing import fake_ipregistry

def test_french_visitor(client):
    stubs = {"*": {"location": {"country": {"code": "FR", "name": "France"}}}}
    with fake_ipregistry(stubs) as fake:
        response = client.get("/")
        assert "Bienvenue" in response.text
        fake.assert_looked_up("127.0.0.1")

Stub specific IPs, "*" for any IP, "origin" for origin lookups; values can be dicts, IpInfo instances or exceptions. Assertions include assert_looked_up, assert_looked_up_times, assert_not_looked_up, assert_nothing_looked_up, assert_origin_looked_up and assert_user_agents_parsed.

Management command

python manage.py ipregistry_lookup                  # this machine's public IP
python manage.py ipregistry_lookup 8.8.8.8 1.1.1.1  # one or more IPs
python manage.py ipregistry_lookup 8.8.8.8 --fields ip,location --hostname

Resources

License

Apache License 2.0

Metadata

Release files for ipregistry-django 1.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ipregistry-django 1.1.0
File Size Uploaded Provenance Actions
ipregistry_django-1.1.0.tar.gz 21.7 kB With provenance Details for ipregistry_django-1.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for ipregistry-django 1.1.0
File Interpreter ABI Platform Actions
ipregistry_django-1.1.0-py3-none-any.whl Python 3 none any Details for ipregistry_django-1.1.0-py3-none-any.whl

Total release size: 53.0 kB

Release files / ipregistry_django-1.1.0.tar.gz

Download URL ipregistry_django-1.1.0.tar.gz
Size 21.7 kB
Tags Source
SHA-256 checksum
How to use checksums
04f6524a46491e812573685dd56f0fc5f192c34c70c2fe44775b66fd22a6f763
BLAKE2b-256 checksum
How to use checksums
e3cf764864244f13794620577bb6a622bfda828d0b9a61770ca3569cdb337384
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

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 Jul 9, 2026.

Transparency log

Release files / ipregistry_django-1.1.0-py3-none-any.whl

Download URL ipregistry_django-1.1.0-py3-none-any.whl
Size 31.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c23c925ef99c665500649ca860968cee58bc6406afe11192274c466d582f1db8
BLAKE2b-256 checksum
How to use checksums
229be7b85adea14cf456818aa83de175120b0e2afcec012a9c89c92b980e58f7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

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 Jul 9, 2026.

Transparency log

Security for ipregistry-django 1.1.0

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.

Select from 2 files

Attestation for ipregistry_django-1.1.0.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 Jul 9, 2026.

Transparency log

A 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.

Release history Release notifications | RSS feed

This release

1.1.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page