Skip to main content

django-icv-core

CI PyPI version Python versions Django versions License: MIT

django-icv-core is the foundation layer for the ICV Django ecosystem. It gives every model in your project a UUID primary key, auto-managed timestamps, and optional soft-delete behaviour — with zero boilerplate. Add the optional audit subsystem and you get immutable event logs, admin activity tracking, and system alerts with a single setting.

Use it as a standalone package or as the base for other django-icv-* packages.


Features

  • BaseModel — UUID primary key (v4 by default, v7 opt-in) and auto-managed created_at/updated_at timestamps
  • SoftDeleteModel — Safe record removal with soft_delete() and restore(); default manager excludes deleted records automatically
  • SoftDeleteManager / SoftDeleteQuerySet.active(), .deleted(), .with_deleted() on every soft-delete model
  • CurrentUserMiddleware — Thread-local request user for automatic created_by/updated_by population
  • Audit subsystem (opt-in via ICV_CORE_AUDIT_ENABLED) — Immutable AuditEntry, AdminActivityLog, SystemAlert, AuditMixin, @audited decorator, and management commands
  • Template tagscents_to_currency, cents_to_amount, time_since_short
  • Django system checks — Configuration validation at startup
  • Test utilitiesicv_core.testing provides factory-boy factories and pytest fixtures for consuming projects

Requirements

  • Python 3.11+
  • Django 5.2 or 6.0

Installation

pip install django-icv-core

Add icv_core to INSTALLED_APPS:

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

Run migrations:

python manage.py migrate

Note: The audit subsystem tables are only created when ICV_CORE_AUDIT_ENABLED = True. Add django.contrib.contenttypes to INSTALLED_APPS before enabling audit.


Quick Start

BaseModel — UUID primary keys and timestamps

Inherit from BaseModel and every record gets a UUID primary key and automatic timestamps. No extra fields to define.

from django.db import models
from icv_core.models import BaseModel


class Article(BaseModel):
    title = models.CharField(max_length=255)
    body = models.TextField()

    class Meta(BaseModel.Meta):
        verbose_name_plural = "articles"
article = Article.objects.create(title="Hello, world", body="...")

article.id          # UUID4: e.g. '3f2504e0-4f89-11d3-9a0c-0305e82c3301'
article.created_at  # datetime — set on creation, never changes
article.updated_at  # datetime — updated automatically on every save

SoftDeleteModel — safe record removal

Records are never hard-deleted by default. soft_delete() sets is_active=False and records the timestamp. The default manager silently excludes deleted records from all queries.

from icv_core.models import SoftDeleteModel


class Subscription(SoftDeleteModel):
    plan = models.CharField(max_length=50)
    customer_email = models.EmailField()
sub = Subscription.objects.create(plan="pro", customer_email="user@example.com")

# Soft-delete: hides the record from default queries
sub.soft_delete()
sub.is_active   # False
sub.deleted_at  # datetime

# Default manager returns active records only
Subscription.objects.all()          # excludes deleted records
Subscription.objects.active()       # same as above — explicit alias

# Access deleted or all records when needed
Subscription.objects.deleted()      # deleted records only
Subscription.objects.with_deleted() # everything
Subscription.all_objects.all()      # raw manager — no filtering applied

# Restore
sub.restore()
sub.is_active   # True
sub.deleted_at  # None

Hard deletion is blocked unless you explicitly allow it:

# Raises ProtectedError by default
sub.delete()

# Permanent removal — use deliberately
sub.hard_delete()

# Or allow .delete() project-wide
ICV_CORE_ALLOW_HARD_DELETE = True

CurrentUserMiddleware — automatic created_by/updated_by

Add the middleware to make the current request user available to models without passing it through every service call.

# settings.py
MIDDLEWARE = [
    # ...
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "icv_core.middleware.CurrentUserMiddleware",  # must come after AuthenticationMiddleware
    # ...
]

ICV_CORE_TRACK_CREATED_BY = True
from icv_core.middleware import get_current_user

user = get_current_user()  # returns None outside of a request context

Audit subsystem

Enable the audit subsystem in settings:

ICV_CORE_AUDIT_ENABLED = True

AuditEntry — an immutable record of a system event. Raises ImmutableRecordError on update and ProtectedError on delete.

from icv_core.audit.services import log_event
from icv_core.audit.models import AuditEntry

# Record a security event
log_event(
    event_type=AuditEntry.EventType.SECURITY,
    action=AuditEntry.Action.PERMISSION_DENIED,
    user=request.user,
    description="Attempted access to restricted resource.",
    metadata={"path": request.path},
)

AuditMixin — add to any model to track CREATE, UPDATE, and DELETE automatically:

from icv_core.audit.mixins import AuditMixin
from icv_core.models import BaseModel


class Contract(AuditMixin, BaseModel):
    title = models.CharField(max_length=255)
    value = models.DecimalField(max_digits=10, decimal_places=2)

@audited decorator — wrap a view or service function to record its execution:

from icv_core.audit.decorators import audited
from icv_core.audit.models import AuditEntry


@audited(event_type=AuditEntry.EventType.DATA, action=AuditEntry.Action.DELETE)
def cancel_membership(user, membership):
    membership.soft_delete()

SystemAlert — raise and resolve operational alerts:

from icv_core.audit.services import raise_alert, resolve_alert
from icv_core.audit.models import SystemAlert

alert = raise_alert(
    alert_type=SystemAlert.AlertType.PAYMENT,
    severity="error",
    title="Payment processor unreachable",
    message="Stripe API returned 503 for the last 5 minutes.",
)

# Later, once resolved
resolve_alert(alert, resolved_by=request.user, notes="Stripe incident resolved.")

Tenancy (deprecated)

icv_core.tenancy (TenantAwareMixin, TenantOwnedMixin, TenantScopedManager, and the get/set/clear_current_tenant context helpers) is deprecated and will be removed in a future release. Subclassing either mixin now emits a DeprecationWarning.

Use django-boundary instead. It provides row-level isolation backed by PostgreSQL Row Level Security, automatic query filtering, and a configurable tenant FK name.

Deprecated (icv_core.tenancy) Replacement (django-boundary)
TenantAwareMixin (FK on_delete=PROTECT) boundary.models.TenantModel
TenantOwnedMixin (FK on_delete=CASCADE) boundary.models.TenantModel (CASCADE is the default)
TenantScopedManager (explicit .for_tenant()) boundary.models.TenantManager (automatic filtering)
get_current_tenant() boundary.context.TenantContext.get()
set_current_tenant() boundary.context.TenantContext.set()
clear_current_tenant() boundary.context.TenantContext.clear()
tenant_context() boundary.context.TenantContext.using()

Template tags

{% load icv_core %}

{# Format pence/cents as a currency string #}
{{ order.total_pence|cents_to_currency:"GBP" }}  {# £35.00 #}
{{ order.total_pence|cents_to_currency:"USD" }}  {# $35.00 #}

{# Raw decimal amount without currency symbol #}
{{ order.total_pence|cents_to_amount }}           {# 35.00 #}

{# Short human-readable relative time #}
{{ comment.created_at|time_since_short }}         {# 2h ago / 3d ago / just now #}

Settings reference

All settings use the ICV_CORE_ prefix. Every setting has a sensible default — only override what you need.

Core

Setting Default Description
ICV_CORE_UUID_VERSION 4 UUID version for primary keys. 4 = random; 7 = time-sorted (requires Python 3.12+)
ICV_CORE_ALLOW_HARD_DELETE False When True, .delete() performs a hard delete on SoftDeleteModel instead of raising ProtectedError
ICV_CORE_TRACK_CREATED_BY False Enable created_by/updated_by tracking. Requires CurrentUserMiddleware
ICV_CORE_DEFAULT_ORDERING "-created_at" Default ordering applied to BaseModel subclasses

Audit

Setting Default Description
ICV_CORE_AUDIT_ENABLED False Master switch. No tables are created and no signals connect when False
ICV_CORE_AUDIT_RETENTION_DAYS 365 Days before audit entries are eligible for archival
ICV_CORE_AUDIT_EXCLUDE_MODELS [] Models excluded from AuditMixin auto-tracking. Format: ["app_label.ModelName"]
ICV_CORE_AUDIT_TRACK_FIELD_CHANGES True Capture old and new field values on UPDATE
ICV_CORE_AUDIT_CAPTURE_IP True Record the request IP address in audit entries
ICV_CORE_AUDIT_CAPTURE_USER_AGENT True Record the request user agent in audit entries
ICV_CORE_AUDIT_AUTO_MODEL_TRACKING False Automatically log CREATE/UPDATE/DELETE on all BaseModel subclasses
ICV_CORE_AUDIT_ALERT_SEVERITY_LEVELS ["info", "warning", "error", "critical"] Available severity levels for SystemAlert

Management commands

Command Description
icv_core_check Validate package configuration and emit any warnings
icv_core_audit_archive Archive audit entries older than ICV_CORE_AUDIT_RETENTION_DAYS
icv_core_audit_stats Print a summary of audit entry counts by event type and action

Testing utilities

icv_core.testing provides factory-boy factories and pytest fixtures for use in your own project's test suite.

# In your tests
from icv_core.testing.factories import AuditEntryFactory, SystemAlertFactory

entry = AuditEntryFactory(action="CREATE", event_type="DATA")
alert = SystemAlertFactory(severity="critical", alert_type="payment")

Import the included pytest fixtures by adding icv_core.testing.fixtures to your conftest.py:

# conftest.py
pytest_plugins = ["icv_core.testing.fixtures"]

Signals

icv-core emits the following signals. Connect to them from your own apps for loose coupling.

Signal Sent when
icv_core.signals.pre_soft_delete Before a record is soft-deleted
icv_core.signals.post_soft_delete After a record is soft-deleted
icv_core.signals.pre_restore Before a soft-deleted record is restored
icv_core.signals.post_restore After a soft-deleted record is restored
icv_core.audit.signals.audit_entry_created After an AuditEntry is written
icv_core.audit.signals.system_alert_raised After a SystemAlert is raised
icv_core.audit.signals.system_alert_resolved After a SystemAlert is resolved

Licence

MIT — see LICENSE.

Download files

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

Source Distribution

django_icv_core-0.4.0.tar.gz (55.8 kB view details)

Uploaded Source

Built Distribution

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

django_icv_core-0.4.0-py3-none-any.whl (45.4 kB view details)

Uploaded Python 3

File details

Details for the file django_icv_core-0.4.0.tar.gz.

File metadata

  • Download URL: django_icv_core-0.4.0.tar.gz
  • Upload date:
  • Size: 55.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for django_icv_core-0.4.0.tar.gz
Algorithm Hash digest
SHA256 a53c0513a8f884ea1c46465d72e0f85df52e13183ea2ccad1b2de25a7df03943
MD5 4977e219b4cc0a9c7eb248e98dcdd907
BLAKE2b-256 f5b75b25f2385637263f6df68955a1d94625b3b959c6f10ef93cd6ee3fa94fa0

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_icv_core-0.4.0.tar.gz:

Publisher: publish.yml on icvoss/django-icv-core

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file django_icv_core-0.4.0-py3-none-any.whl.

File metadata

File hashes

Hashes for django_icv_core-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 6117c1179cae11ec3824f33b19a738663ba90158299bc207946d8212eb2f4883
MD5 a40ab674dd19eb377401aeb7324be95e
BLAKE2b-256 2e84610871475dee80b56dff3a298b1298b75de27ab1f92f54c0e4c722e75359

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_icv_core-0.4.0-py3-none-any.whl:

Publisher: publish.yml on icvoss/django-icv-core

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.5.0

2 files

0.4.5

2 files

0.4.4

2 files

0.4.3

2 files

0.4.2

2 files

0.4.1

2 files

This release

0.4.0 This release

2 files

0.3.0

2 files

0.2.0

2 files

0.1.0

2 files

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