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 tags: cents_to_currency, cents_to_amount, time_since_short
  • Django system checks: Configuration validation at startup
  • Test utilities: icv_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 icv-core 1.0.0 per ADR-025 (ruling T3). 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.1.tar.gz (56.3 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.1-py3-none-any.whl (46.3 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: django_icv_core-0.4.1.tar.gz
  • Upload date:
  • Size: 56.3 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.1.tar.gz
Algorithm Hash digest
SHA256 6f86d79a4aabaa431b9dda7248215e0cc69a07c8ec4a918eb1b6c84513678fef
MD5 d3c0e794555d6942b4d10e82cb474f93
BLAKE2b-256 4c76244301c98e06f359929c7e2a5343468fc2a212713a7c78a2a0242e3bf8e7

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_icv_core-0.4.1.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.1-py3-none-any.whl.

File metadata

  • Download URL: django_icv_core-0.4.1-py3-none-any.whl
  • Upload date:
  • Size: 46.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for django_icv_core-0.4.1-py3-none-any.whl
Algorithm Hash digest
SHA256 1dd1d5dfec701391e62b449811fa1bb73f4d0b2f0e1332c8470822ee4940b734
MD5 fc749aebe29ccb41af4bb5c4dcd80852
BLAKE2b-256 b295775fc79461600f36841340ace0e1e9fdbc657f6515f4ebe158a619d1d490

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_icv_core-0.4.1-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

This release

0.4.1 This release

2 files

0.4.0

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