Zibanu Django Logging
Optional Zibanu Django package that persists application events, mail-delivery status and per-model audit trails to the database.
Overview
zibanu.django.logging turns runtime events into stored database rows. It provides three complementary mechanisms:
- Database log handler —
DbHandler, alogging.Handlersubclass referenced from Django'sLOGGINGconfiguration, so records emitted through the standardloggingmodule are written asLogrows. - Mail logging — the
mail_sentsignal (its historical name,send_mail, is kept as a deprecated alias — see API Reference) and itson_mail_sentreceiver record each outgoing mail's SMTP result as aLogplus a linkedMailLog. - Model auditing —
AuditEntityrows select which models are audited and on which events; a signal factory wires theaudit_modelreceiver to those models'post_save/pre_save/post_deletesignals, writing aLogrow (with old/new values) for each change. The set of audited models is managed from the Django admin.
This package exposes no REST endpoints; it is a persistence and signal layer only.
Installation
Install the package from its standalone installer:
pip install ./installers/zibanu-django-logging/
Add it to INSTALLED_APPS in mainApp/components/base.py (the app label is zb_logging):
INSTALLED_APPS = [
# ... existing apps ...
"zibanu.django.logging",
]
Run migrations to create the Log, MailLog and AuditEntity tables:
python manage.py migrate
Quick Start
Route the standard logging module into the database by adding the handler to your Django LOGGING setting:
LOGGING = {
"version": 1,
"disable_existing_loggers": True,
"formatters": {
"simple": {
"format": "[{levelname}] - {asctime} - {module}: {message}",
"style": "{",
},
},
"handlers": {
"zibanu": {
"level": "DEBUG",
"class": "zibanu.django.logging.lib.handlers.DbHandler",
"formatter": "simple",
},
},
"loggers": {
# Do not route the SQL logger through DbHandler: emit() itself runs an
# INSERT, so letting django.db.backends propagate to the root handler
# would feed the handler its own queries.
"django.db.backends": {
"handlers": [],
"propagate": False,
},
},
"root": {
"handlers": ["zibanu"],
"level": "DEBUG",
},
}
Transactional semantics.
DbHandlerwrites synchronously on the caller's database connection, inside the caller's transaction: a record logged within anatomic()block that rolls back is lost with the rollback, and once a transaction is broken the insert fails and the record is dropped (routed tohandleError). Records that must survive a rollback need an out-of-band channel (file handler, external service).
Any log call now creates a Log row:
import logging
logging.getLogger(__name__).info("User profile updated")
Features
DbHandlerlogging handler that persists each emittedLogRecordas aLogrow; failures are routed tohandleError, so logging never breaks the caller.mail_sentsignal +on_mail_sentreceiver that record SMTP delivery status (Log+MailLog) atomically. Deliberately not resilient (unlikeDbHandlerandaudit_modelbelow): a persistence failure propagates, with its original cause intact, to whoever calledmail_sent.send().- Per-model auditing driven by
AuditEntityconfiguration, with create / update / delete captured asLogrows including serializednew_valuesand (on update)old_values. - Audit detail serialization is resilient: an unserializable field value degrades the row's
detailto an error payload instead of aborting the audited model'ssave()/delete(), and GIS geometry fields (GEOSGeometry/Point/Polygon/MultiPolygon, via the optionalzibanu.django.gispackage) are serialized as GeoJSON. - Sensitive fields (
password,token,secret,api_key,private_keyby default) are masked with a fixed placeholder before an audit payload is written, including inside any model nested in the dump — configurable viaZB_LOGGING_REDACTED_FIELDS. - Runtime rewiring of audit signals from the Django admin — no restart needed when the audit configuration changes.
- Startup wiring deferred to a background thread that waits for the app registry, so audit signals attach only once every app is fully loaded.
Usage
Basic Usage — logging to the database
With DbHandler configured (see Quick Start), standard logging calls are persisted. To attach a user and source IP to a record, pass a single dict as the log arguments — DbHandler.emit reads user_id and ip_address from it when record.args is a dict:
import logging
logging.getLogger(__name__).info(
"Sensitive action performed",
{"user_id": request.user.id, "ip_address": "203.0.113.7"},
)
Basic Usage — recording mail delivery
Dispatch the mail_sent signal after attempting to send a mail; the on_mail_sent receiver stores a Log and a linked MailLog:
from zibanu.django.logging.lib.signals import mail_sent
mail_sent.send(
sender=MyMailerClass,
mail_from="noreply@example.com",
mail_to=["user@example.com"], # list; stored joined by ";"
subject="Welcome",
smtp_error="",
smtp_code=0,
)
Advanced Usage — auditing a model
- Ensure
zibanu.django.loggingis inINSTALLED_APPSand migrated. - In the Django admin, open Audit Entities, add an entry, pick the target model (dotted
app_label.ModelName) from the live choice list, and check the events to audit (on_create,on_update,on_delete). Keepenabledchecked. - Saving the entity calls
signals_factory(), which rewires the audit signals immediately — no restart required.
From then on, each configured event writes exactly one Log row — an UPDATE fires both pre_save (on_update) and post_save (on_create, created=False) on the audited model, but only the former populates detail, so the event is recorded once, not twice. That row's detail is a JSON payload containing new_values (and, for updates on an existing row, old_values), with the acting user and source IP resolved from the current request stack.
Audit entities can also be created programmatically:
from zibanu.django.logging.models import AuditEntity
AuditEntity.objects.create(
model_name="auth.User",
on_create=True,
on_update=True,
on_delete=True,
enabled=True,
)
# Rewire signals so the new rule takes effect without a restart:
from zibanu.django.logging.lib import signals_factory
signals_factory()
Behavior — disabling an entity. Calling
signals_factory()without amodelargument processes everyAuditEntityrow, enabled or not: each one is unconditionally disconnected first, then reconnected only if it is stillenabled. So setting an entity'senabled=Falsedoes disconnect its previously wired handlers, the next timesignals_factory()runs with nomodelargument — which happens automatically on every admin save/delete of any entity, including the one just disabled. Deleting the entity (signals_factory(model=obj, delete=True)) also disconnects it, immediately and without waiting for a general rewire.
API Reference
Models (zibanu.django.logging.models)
Log— extendszibanu.django.db.DatedModel(autocreated_at/modified_at). Fields:action,sender,detail(text, default""),ip_address(nullable),user(FK to the user model, nullable,on_delete=SET_NULL— the log row survives deletion of the referenced user). Indexed onuser,actionandcreated_at.MailLog— extendszibanu.django.db.Model. One-to-oneloglink toLogwithon_delete=PROTECT(aLogcannot be deleted while aMailLogreferences it). Fields:mail_from,mail_to,subject,smtp_code(default0),smtp_error(nullable).AuditEntity— extendszibanu.django.db.Model. Fields:model_name(unique dottedapp_label.ModelName), booleanson_create/on_update/on_delete, andenabled(defaultTrue). Ordered bymodel_name.save()normalizesmodel_namebefore persisting: a name that resolves against the app registry (case-insensitively) is rewritten to the model's canonicalModel._meta.label, so case variants of the same model collapse to one string andunique=Trueholds under any database collation; a name that does not resolve (stale or mistyped) is instead lower-cased in place — it still can't be resolved to a canonical label, but folding its case still makes two variants of the same unresolvable name collide underunique=Trueregardless of the database's collation.
Database engine. All three models persist exclusively through the Django ORM on top of the base package's
Model/DatedModelclasses — no raw SQL, no engine-specificRunSQLmigration operations, no required database extension, and no declared dependency on a concrete driver (psycopg,mysqlclient,oracledb, …). The database alias is resolved through the base package'suse_dbattribute (defaults to"default", never overridden here); this package declares noDATABASE_ROUTERSof its own, so the host project's multi-database routing is respected as-is.
Retention. This package provides no purge mechanism for
Log,MailLogorAuditEntity: no management command, no periodic task, no signal or receiver that deletes rows, and noTTL/archival/retention-by-row-count setting. A row is written once, and the package itself never modifies or deletes it afterwards — the only two ways a row disappears are a manual delete through the Django admin (standarddelete/delete_querysetpermission, unrestricted by this package — see Admin below) or a direct action by a database administrator, both outside the package's scope. Sizing and retention policy for theLog/MailLogtables are a decision of the host project, not of this package.
Logging handler (zibanu.django.logging.lib.handlers)
DbHandler(Handler)— subclasseslogging.Handler(notStreamHandler).emit(record)builds aLogfrom the record (action= level name,sender=module/funcName,detail= formatted message, truncated toZB_LOGGING_DETAIL_MAX_LENGTHcharacters with a truncation marker — see Configuration), readsuser_id/ip_addressfromextra=attributes on the record (falling back torecord.argswhen it is a dict), and inserts the row withforce_insert=True. Exceptions are sent tohandleErrorinstead of being raised.
Signals and receivers (zibanu.django.logging.lib)
mail_sent(zibanu.django.logging.lib.signals) —django.dispatch.Signaldispatched when a mail is sent. Expected keyword arguments:mail_from(str),mail_to(list),subject(str),smtp_error(str),smtp_code(int).send_mailis kept as a deprecated alias of the sameSignalobject (the historical, README-documented name, which collides withdjango.core.mail.send_mail) — it is expected to be removed in the next release; usemail_sent.on_mail_sent(sender, mail_from, mail_to, subject, smtp_error, smtp_code, **kwargs)(zibanu.django.logging.lib.receivers) — receiver connected tomail_sent; creates aLogand a linkedMailLoginside a single transaction. ReturnsNone. Accepted exception to this package's resilience rule: there is notry/exceptaround this receiver — unlikeDbHandler.emit(routes failures tohandleError) andaudit_model(catches broadly and logs at error level), a persistence failure here propagates, with its original cause intact, to whoever calledmail_sent.send().audit_model(sender, action=None, **kwargs)(zibanu.django.logging.lib.receivers) — generic audit receiver connected bysignals_factory. Resolves the acting user/IP from the request stack and writes exactly oneLogrow per audited event:new_valuesis populated only for an actual insert (action == "on_create"andcreated=True) or an update (action == "on_update") — thepost_savecall that also fires on every UPDATE withaction="on_create"andcreated=Falseyields an empty payload and inserts nothing, so an UPDATE is logged once (via thepre_save/on_updatewiring), not twice. For updates on an existing row it also capturesold_values, built as a diff: only the fields whose value actually changed, plus the primary key, on both sides — comparison runs on the raw dump so masking (below) never hides a real change, and a save that changed no field writes no row at all. Before serialization, sensitive field values — namedpassword,token,secret,api_keyorprivate_keyby default, configurable viaZB_LOGGING_REDACTED_FIELDS(see Configuration) — are replaced with a fixed placeholder, including inside any model nested in the dump (e.g. a many-to-many row); only model field names are matched, never the keys of a plain mapping such as aJSONFieldpayload. Detail is JSON-encoded withzibanu.django.lib.ExtendedJSONEncoder, which also serializes GIS geometries (GEOSGeometry/Point/Polygon/MultiPolygon) via their.geojsonproperty — duck-typed so this base-package encoder never importsdjango.contrib.gis; if a field value is still unserializable, the resultingTypeErrorfromjson.dumpsis caught anddetaildegrades to{"error": ...}instead of aborting the audited model'ssave()/delete().signals_factory(model=None, delete=False)(zibanu.django.logging.lib) — reads the relevantAuditEntityrows and (re)connectsaudit_modelto each model's signals (on_create→post_save,on_update→pre_save,on_delete→post_delete) using stabledispatch_uids (zb_audit_<model_name>_<action>) andweak=False. Handlers are unconditionally disconnected before reconnecting, so the function is idempotent. RaisesTypeErrorifmodelis given and is not anAuditEntityinstance (norNone) — a programming error is propagated rather than silently reinterpreted as "rewire every entity". Deliberately refuses to wire audit signals onto this package's own models (app_label == "zb_logging"), to avoid aLoginsert re-firing the very signal that created it.ImportErroris logged at warning level (not info: it means the deployment runs completely unaudited) and any other exception at error level; failures never break startup, and are also recorded in the module-levelwiring_errorslist, keyed by lowercasedmodel_name(or a reserved global key for failures not attributable to a single entity, e.g. anImportError) — a no-modelcall clears and repopulates every entry, while a single-entity call (model=<AuditEntity>) clears and repopulates only that entity's entry, leaving every other entity's recorded warning untouched (the global entry is always cleared and retried, regardless of scope) — surfaced as admin warnings byAuditEntityAdmin.changelist_view(see Admin below). See the Behavior note above regarding disabled entities.
Application config (zibanu.django.logging.apps)
ZbDjangoLogging(AppConfig)—name = "zibanu.django.logging",label = "zb_logging".ready()starts a background thread (after_ready()) that blocks on the app-registry ready event and then callssignals_factory(), so audit signals are wired only after all apps are loaded — except for a fixed set of management commands (makemigrations,migrate,collectstatic,makemessages,compilemessages,check,showmigrations,sqlmigrate,inspectdb), detected fromsys.argv, for whichready()returns immediately without starting the thread: none of them write audited models, and some run before the schema (hence theAuditEntitytable) exists. Every other command —runserver,shell,test,createsuperuser, custom commands — still defers normally.after_ready()waits up to 60 seconds onapps.ready_event(falling back to a bounded poll onapps.readyif a future Django release removes that attribute) before callingsignals_factory(); an unexpected exception from the call itself is caught and logged at error level, and if the registry is never marked ready within the timeout, a warning is logged instead and signals are never wired for that process. Either way, the thread closes its own (thread-local) database connections in afinallyblock before exiting.
Admin (zibanu.django.logging.admin, admin_views)
AuditEntityAdmin(ModelAdmin)— registered forAuditEntity. Bulk actions are enabled (including Django's default bulk delete, unlikeLogAdmin/MailLogAdminbelow);list_display = ("model_name", "on_create", "on_update", "on_delete", "enabled"),list_filter = ("enabled",),search_fields = ("model_name",), andfieldsetsgroup the model name, the three audited-event flags andenabledinto separate sections.get_formpopulates themodel_namewidget with the live model choices (get_model_choices), computed once per process and cached (the app registry cannot change without a restart); a blank first option is prepended (otherwise editing an entity whose storedmodel_namefell out of the live choices would make the browser preselect another model and silently repoint the rule on save), and a stale stored value is kept as its own choice labeled "(model no longer available)" instead of disappearing. The field is rebuilt as a plainChoiceFieldso the submitted value is validated against the live choices, not just the widget's rendered options.save_modelcallssuper().save_model()and then unconditionallysignals_factory()(so the rewire acts on the configuration just persisted); when the edit renamesmodel_name, it additionally disconnects the previous model's handlers first — viasignals_factory(model=AuditEntity(model_name=previous_name), delete=True), called beforesuper().save_model()while the DB row still holds the pre-edit name — since the general rewire that follows only processes rows currently in the table and would otherwise never unhook the old model.delete_modelcallssignals_factory(model=obj, delete=True)before deleting;delete_queryset(bulk admin deletes) does the same for every selected entity, mirroringdelete_model.changelist_viewsurfaces any recordedsignals_factory.wiring_errorsas admin warnings, so a broken audit configuration is visible where it is managed.LogAdmin(ModelAdmin)— read-only admin forLog:has_add_permission/has_change_permissionboth returnFalseand every field is listed inreadonly_fields, since rows are evidence written only by the package's handlers/receivers.actions = Nonedisables bulk actions (including bulk delete);list_display = ("created_at", "action", "sender", "user", "ip_address"),list_filter = ("action", "created_at").search_fields = ("^sender",)— a prefix match (istartswith) that can useIDX_logging_log_sender;detailis deliberately excluded from search, since an unindexedLIKEover that never-prunedTextFieldwould turn the change-list search box into an unbounded full scan.MailLogAdmin(ModelAdmin)— read-only admin forMailLog, same policy asLogAdmin(has_add_permission/has_change_permissionreturnFalse,actions = None).list_display = ("log_link", "mail_from", "mail_to", "subject", "smtp_code"),list_filter = ("smtp_code",),search_fields = ("mail_to", "subject"),readonly_fieldscovers every field.log_linkrenders the linkedLogrow as an anchor to its own admin change page.
Configuration
LOGGING(Django setting) — referencezibanu.django.logging.lib.handlers.DbHandleras a handler class to persistloggingoutput to theLogtable (see Quick Start).ZB_LOGGING_DETAIL_MAX_LENGTH(int, default10000) — read byDbHandler.emit. Caps the length of the formatteddetailwritten to theLogrow; a message longer than this is truncated to the limit and appended with a" … [truncated]"marker, so an arbitrarily large formatted traceback cannot be written verbatim into a table that is never pruned.ZB_LOGGING_REDACTED_FIELDS(iterable of str, default:password,token,secret,api_key,private_key) — read byaudit_model. Field names (matched exactly, case-insensitively) whose values are replaced with a fixed placeholder before an audit payload is JSON-encoded intoLog.detail, so credentials never persist there. Replaces the default set entirely rather than extending it; a bare string value is treated as a single field name rather than iterated character by character.- Audit configuration — managed as
AuditEntityrows through the Django admin (or the ORM). No other dedicatedsettings.pykeys are required by this package.
Testing
The package's unit tests live in zibanu/django/logging/tests/ (one test_*.py module per unit under test — the Log/MailLog/AuditEntity models, DbHandler, the mail_sent signal, the audit_model/on_mail_sent receivers, signals_factory, the admin classes and ZbDjangoLogging).
# Run every test of this package
python manage.py test zibanu.django.logging.tests
# Run a specific module, class or method
python manage.py test zibanu.django.logging.tests.test_signals_factory
python manage.py test zibanu.django.logging.tests.test_audit_entity_model.AuditEntitySaveNormalizationTest
# Verbose output
python manage.py test --verbosity=2
Every DB-accessing test (model persistence, DbHandler.emit, the receivers, signals_factory's effect on real rows) runs against the project's own local/dev database — isolation relies on TestCase's per-test transaction rollback, never TransactionTestCase, which would truncate real tables. DB-free units (field/Meta checks, the app-config command-skip gate, the bare signal object) use django.test.SimpleTestCase instead.
Requirements
- Python 3.11+
- Django and Django REST Framework
- The Zibanu base package
zibanu.django— this package importsExtendedJSONEncoder,ModelName,get_model_choices,get_user,get_ip_addressandget_request_from_stackfromzibanu.django.lib/zibanu.django.lib.utils, and the base model classes fromzibanu.django.db.
No dependency on any other optional Zibanu package.
License
Developed by CQ Inversiones SAS. Copyright © 2019–2026. All rights reserved.
Changelog
All notable changes to this package are documented here, one section per principal
version (x.y.z). Pre-releases of the same version (-dev.N, etc.) are folded into
its section.
[2.0.0] — 2026-10-01
No tag exists yet for this version, and the package is still on a pre-release
(2.0.0-dev.5) as of this writing.
Added
- Read-oriented Django admin registered for
LogandMailLog(list/search/filter), plus an audit-entity changelist showing which event hooks (create/update/delete/ save-error) are wired for each entity (LOG-S21, LOG-S25). - Sensitive fields (
password,token,secret,api_key,private_keyby default, configurable viaZB_LOGGING_REDACTED_FIELDS) are now masked before an audited model is serialized intoLog.detail, including fields nested inside a related model value — so a credential on an audited model, or on one it embeds, is never persisted in the clear (LOG-R02, ZBDJ-629, ZBDJ-666, ZBDJ-667). AZB_LOGGING_REDACTED_FIELDSset to a single string is normalized into a one-item list instead of being iterated character by character, which would otherwise mask nothing without any error (ZBDJ-667). AuditEntityAdmin's bulk actions, including Django's default bulk delete, are enabled —delete_querysetalready disconnected each selected entity's audit signals before removal, so no extra wiring was needed (GAP-CFG-02).MailLog's admin list links each row to its associatedLogentry (ZBDJ-685);Log.sendergained a database index andLogAdminnow searches it by prefix (istartswith) instead of a substring match, so the search box can use the new index instead of an unbounded scan (ZBDJ-651).
Changed
- Breaking:
DbHandlernow subclasseslogging.Handlerinstead oflogging.StreamHandler— the correct base for a database sink. A consumer that configured this handler with astreamargument, or relied onStreamHandler-specific behavior, needs to drop that configuration (LOG-S53). - Breaking:
Log.user's foreign keyon_deletechanged fromCASCADEtoSET_NULL, so deleting aUserno longer deletes their audit trail; requires running the accompanying migration (LOG-I7). - Breaking: an update (
on_update) now records only the fields whose value actually changed inold_values/new_values, instead of the full object; both dicts share the same key set, the primary key is always included as an identifier (never counted as a change on its own), and no row is written at all when the diff is empty. Masking is applied to the already-computed diff, not before comparing, so two different values of a redacted field are never collapsed into the same placeholder and silently dropped from the diff.on_create/on_deleteare unaffected and keep logging the full object (ZBDJ-661, ZBDJ-662, ZBDJ-663). - A nested model's own many-to-many fields are excluded from its dump inside an audit payload, so masking a related model no longer issues one extra query per many-to-many field on every related row it holds (ZBDJ-664).
- The receiver connected to
mail_sentwas renamedon_send_mail→on_mail_sent, matching the signal it handles instead of the historicalsend_mailalias; new rows recordLog.action="on_mail_sent"(previously"on_send_mail"— existing rows keep the old value). It now also truncates the sender class name toLog.sender's column width, so an unusually long name can no longer raiseDataErrorfrom inside the signal handler (ZBDJ-628), and normalizes a plain-stringmail_tointo a single-recipient list instead of silently joining it character by character. MailLog.mail_toandMailLog.smtp_errorwidened toTextField, so a long recipient list or a real multi-line SMTP rejection message no longer overflows on insert (LOG-I8, LOG-S29).MailLog.smtp_errordroppednull=Truein favor of a single "no value" representation,default=""; existingNULLrows are backfilled by the accompanying migration (LOG-S14, LOG-R05).- Added an index on
Log.created_atfor retention/cleanup queries, and normalizedAuditEntity.model_namestorage so it agrees with its case-sensitive lookup; a name that still fails to resolve against the app registry (typo, an app not yet installed) is now lower-cased rather than stored verbatim, so two case-variants of the same unresolvable name collide underunique=Trueregardless of the database's collation (LOG-S30, LOG-S31, GAP-CFG-03). - Internal
libpackage surface cleaned up: wildcard re-exports replaced with explicit imports and__all__at each level; the public entry pointzibanu.django.logging.lib.signals_factoryis unchanged (LOG-I22, LOG-I23, LOG-S34–S37, LOG-S51, LOG-S52). - Admin: the model-choices list for audit entities is cached instead of walking
the app registry on every render (LOG-S23); editing an entity whose stored
model_nameis no longer among the live choices now keeps that stale value visible (labeled as no longer available) instead of letting the widget silently substitute another model on save, and renamingmodel_nameon an existing entity now disconnects the previous model's signal handlers before the rename is persisted. audit_modelnow isolates its own failures (catches and logs at error level, mirroringDbHandler) instead of letting an unexpected exception propagate into the audited model's ownsave()/delete();on_mail_sentis deliberately left unwrapped, so a persistence failure there still propagates to the caller (LOG-R05).- Signal-wiring failures are now tracked per audited entity rather than as one shared list: deleting or disabling a single entity clears only that entity's recorded failure, instead of blanking the warnings recorded for every other entity.
Fixed
- Admin: entity save/signal rewiring now runs after
super().save_model(), so it reads post-save state instead of stale pre-save state; a crafted admin POST can no longer target an arbitrary model by name; bulk-deleting entities from the admin now unwires each deleted entity's signal handlers (LOG-C1, LOG-I12, LOG-S24). - App startup: the deferred signal-wiring thread is now a bounded, daemon thread
that closes its own database connection, so it can no longer block interpreter
shutdown or leak a connection (LOG-I9, LOG-I11); the gate that skips wiring
during management commands (
migrate,makemigrations, etc.) now correctly recognizes the subcommand even when it follows a leading option (e.g.manage.py --settings=x migrate). - Signal wiring: disabling an audit entity now disconnects all of its rows (previously some stayed connected); a wiring failure on one entity no longer aborts wiring for every entity after it; wiring failures are now recorded in an observable list instead of only being logged (LOG-I13, LOG-I14, LOG-S38).
- Change detection: object creation is now detected from
instance.pkinstead of acreatedflag thatpre_savenever actually receives, and delete logging is now populated from the in-memory instancepost_deletestill holds — both previously produced inaccurate or missing audit rows (LOG-I16, LOG-I17). DbHandler.emit: a thread-local guard prevents an INSERT-triggered log record from recursively re-enteringemit()(LOG-I20).audit_modelno longer raises when a field holds a value the JSON encoder can't serialize (e.g. a GIS geometry) — it now degrades to an{"error": ...}detail instead of propagating the exception into the caller'ssave()/delete()(LOG-C2).- Duplicate/mislabeled audit rows on updates: an
UPDATEwas writing a second row incorrectly labeledon_create, becausepost_savefires withcreated=Falseon every update, not just inserts.new_valuesis now populated only for a genuine create or update, and no row is written when the resulting detail is empty. - Fixed a name collision where
lib/utils/__init__.py'sfrom .signals_factory import signals_factoryre-export shadowed thesignals_factorysubmodule with the function of the same name, so code that imported the module itself (to read itswiring_errorslist) would have raisedAttributeError. The submodule was renamed tosignals_factory_module.py; the publiczibanu.django.logging.lib.signals_factoryfunction is unchanged.
Documentation
- Transaction/rollback semantics of
DbHandler, and the recommendedLOGGINGexample excludingdjango.db.backends, documented in the README (LOG-I20, LOG-I21). - README (EN/ES) updated to describe the one-row-per-event guarantee and the GIS-geometry serialization fix above.
MailLog.smtp_error's empty-string default documented as the field's only "no value" representation (no moreNone), following thenull=Trueremoval above (LOG-S14).- README corrected to describe disabling an audit entity accurately: a
signals_factory()call with nomodelargument processes every entity (enabled or not), disconnecting each one first and reconnecting only the still-enabled ones — so disabling an entity does disconnect it, on the next general rewire (which happens on every admin save/delete of any entity), not only when the entity is deleted (ZBDJ-656). - README documents that
on_mail_sentis deliberately excluded from this package's general exception-resilience rule (ZBDJ-683), and its own docstrings and themail_sentsignal's docstring now reference the current receiver name (ZBDJ-678). - README documents
DbHandler's transactional semantics (ZBDJ-648) and updates the recommendedLOGGINGexample (README.es.md) to excludedjango.db.backendsfrom the routing toDbHandler(ZBDJ-643). - README documents the cleanup scope of the wiring-failure inventory and the
ImportError-at-info-level detail (ZBDJ-674), the three models' database-engine agnosticism (ZBDJ-636), and the absence of automatic purging — retention is the hosting project's responsibility (ZBDJ-634). - README (EN/ES) refreshed for internal consistency after the
GAP-CFG-01/GAP-CFG-02/GAP-CFG-03changes above and several other prose/behavior divergences found in the same pass.
[1.2.5] — never released (superseded by 2.0.0)
Only ever existed as 1.2.5-dev.0: development jumped straight to 2.0.0-dev.0
without ever releasing a 1.2.5, so no consumer could have installed this
version.
Added
LogandMailLoggained__str__representations (LOG-S13).
Changed
AuditEntity.model_nameuniqueness now enforced withunique=Truedirectly, replacing a single-fieldunique_together;Log/MailLogsaves wrapped intransaction.atomic()(LOG-S12, LOG-S6).- Internal cleanup: dropped a redundant
.all()on an already-filtered queryset, renamed a loop variable that shadowed themodelparameter, replacedzip(get_model_list(), get_model_list())withget_model_choices(), and removed several unused imports (LOG-S1, LOG-S2, LOG-S9, LOG-S5, LOG-S15, LOG-S17, LOG-S18).
Fixed
- Signal wiring:
dispatch_uidis now stable and connects withweak=False, so a later disconnect actually matches the original connect — previously this could leave stale handlers or silently fail to reconnect (LOG-I1). DbHandler.emit: guardsrecord.argsbefore treating it as a mapping, and now callsself.handleError(record)on failure instead of silently swallowing the exception (LOG-I2, LOG-I3).- Mail logging: SMTP errors are now recorded on the
MailLogrow instead of being reset toNonebefore saving (LOG-I4). - Admin: the model-choices list for audit entities is now built per request instead of being frozen at import time, so a newly-registered model appears without restarting the process (LOG-I5).
- The acting user is now coerced to
Nonebefore being assigned toLog.userfor anonymous requests, instead of assigning an anonymous-user object incompatible with the foreign key (LOG-S7). - Mail-sending logs now use the literal
action="on_send_mail"instead of inspecting the call stack withinspect.currentframe()(LOG-S8).
Documentation
- Full NumPy-style docstrings added across models,
DbHandler, signal receivers,signals_factory,AppConfig, and admin views; README rewritten and a Spanish translation (README.es.md) added. Thesignals_factoryconnect/disconnect asymmetry was documented as real, intentional behavior — no logic changed by this pass. - Documented why the deferred signal wiring runs on the
AppConfig'sready_eventthread rather than inready()itself (LOG-S16).
[1.2.4] — 2025-03-18
There is no 1.2.4 tag. Note that the repository's only tag, 1.2.3, actually
marks a later point (dated 2025-08-27) at which the package version already
read 1.2.4 — the tag name is out of sync with the version it marks, so it
cannot be used to identify what 1.2.3 contained.
Changed
- Admin app label changed from "Zibanu Logging" to "Zibanu Django Logging"
(
apps.py,verbose_name).
Note: the commit that introduced this version is titled after a GIS model, but its actual diff touches only the version file and the one-line admin label change above — no GIS code. This looks like a commit-message artifact carried over when this package's history was extracted from the former Zibanu Django monorepo into its own repository, not an undocumented feature; read as such rather than as evidence of a missing GIS-related change in this package.
[1.2.3] — 2025-03-14
Initial commit of this package as its own repository, extracted from the former Zibanu Django monorepo. No earlier git history is available in this repository to derive changes from — this section marks the starting point of the tracked history, not a diff.
Metadata
Release files for zibanu-django-logging 2.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| zibanu_django_logging-2.0.0.tar.gz | 84.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| zibanu_django_logging-2.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 151.2 kB
Release files / zibanu_django_logging-2.0.0.tar.gz
| Download URL | zibanu_django_logging-2.0.0.tar.gz |
|---|---|
| Size | 84.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
47a1988e80ad8a70172fbf472d30bde90038dde80fcaa5cb7c597b54863a8fe5
|
|
BLAKE2b-256 checksum How to use checksums |
36d14c337ec02ec49559815e3440aec59ded69037e6b158dab41fe5a55c4b57e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.13
|
Release files / zibanu_django_logging-2.0.0-py3-none-any.whl
| Download URL | zibanu_django_logging-2.0.0-py3-none-any.whl |
|---|---|
| Size | 66.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d319093d713978f0a94ac500c98ba626b70b06708b4ba3e069f9bccf82cf796c
|
|
BLAKE2b-256 checksum How to use checksums |
bdee26fae92a798ea15d5a3770870bb56d9597193d877d5757c2059654eb8863
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.13
|