Skip to main content

Official Django integration for the Ipregistry IP geolocation and threat data API.

Project description

Ipregistry Django

License CI PyPI

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

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 helperis_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.
  • Testablefake_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 with a 403. Anonymizing services (anonymous, proxy, relay, tor, vpn) are opt-in signals.

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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ipregistry_django-1.0.0.tar.gz (21.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ipregistry_django-1.0.0-py3-none-any.whl (30.9 kB view details)

Uploaded Python 3

File details

Details for the file ipregistry_django-1.0.0.tar.gz.

File metadata

  • Download URL: ipregistry_django-1.0.0.tar.gz
  • Upload date:
  • Size: 21.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.25 {"installer":{"name":"uv","version":"0.11.25","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Fedora Linux","version":"44","id":"","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for ipregistry_django-1.0.0.tar.gz
Algorithm Hash digest
SHA256 4b4e771ff406e7789f8a794bb433fb569dca6c7949906c1184f190a40eb9988a
MD5 b76d8f9e33862ed4ab84ab1bb570dd00
BLAKE2b-256 8eab8af20545f8e45ce2122dd02138b5794bc004184ea45fa35bfee22e7b5917

See more details on using hashes here.

File details

Details for the file ipregistry_django-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: ipregistry_django-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 30.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.25 {"installer":{"name":"uv","version":"0.11.25","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Fedora Linux","version":"44","id":"","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for ipregistry_django-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 5a96c30ae03d799e1344fb13f55e263f952b783964138e5bb0201edb941f90ae
MD5 8506795cb2f013ab5f228be5f484655f
BLAKE2b-256 d2d3b60bd07208a2099cd39a555841d28780f0506102d2b93b6302ba63d69ad4

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page