ICVOSS DJANGO PACKAGE REGISTRY

The package index No.02

django-icv-core

v0.4.0 on PyPI

Foundation layer for ICV-Django: abstract base models (UUID PK, timestamps, soft-delete), middleware, audit logging, and test utilities.

pip install django-icv-core

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


Requirements


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.