zibanu.django
Base framework package of Zibanu Django: ORM base classes, Django REST Framework extensions, and shared utilities that every optional package builds on.
Overview
zibanu.django is the always-installed foundation of the Zibanu Django framework.
It has no dependency on any optional package (auth, repository, logging,
template, gis, mqtt, state_machine) — each of those depends on this one, never
the other way around. Its own runtime dependencies are Django, Django REST Framework,
Pillow and drf-extra-fields; SimpleJWT is not imported at module level — ViewSet
and ModelViewSet resolve JWTTokenUserAuthentication lazily, per request, only when
django.apps.apps.is_installed("zibanu.django.auth") — so the base package installs
and runs with no optional package present at all.
It provides four areas:
zibanu.django.db— aModel/DatedModelbase class pair adding multi-database routing and a recursive bulk-update helper, aManagerhonoring that routing, aPhoneFieldmodel field, and a custom Oracle backend for reserved-word column names.zibanu.django.rest_framework— aViewSet/ModelViewSetpair with this project's own authentication/permission defaults and error envelope, apermission_requireddecorator, custom exceptions, aCurrentUserDefaultfield compatible with SimpleJWT'sTokenUser, and aHybridImageFieldwith size validation.zibanu.django.lib— shared classes (BaseModelController,CodeGenerator,ExtendedJSONEncoder,ModelName,RegEx), theDialCodesenumeration, request- context middleware, and utility functions (mail, files, dates, model introspection, users, signal receivers).zibanu.django.api— three ready-to-mount REST endpoints: country dial codes, Django's configured languages, and available timezones.
Installation
pip install ./installers/zibanu-django/
Add it to INSTALLED_APPS in your project's settings (it is required by every
optional Zibanu Django package, so in practice it is always present):
INSTALLED_APPS = [
# ...
"zibanu.django",
]
zibanu.django does not ship any model of its own, so no migration is required just
for installing it.
Quick Start
Build a model on the project's own base class instead of django.db.models.Model, to
get multi-database routing and the recursive set() helper for free:
from zibanu.django.db.models import DatedModel, models
class Article(DatedModel):
title = models.CharField(max_length=200)
body = models.TextField()
Expose it through a ModelViewSet, whose actions are reached through your own
@action-decorated POST endpoints (see API Reference):
from zibanu.django.rest_framework.viewsets import ModelViewSet
from rest_framework.decorators import action
class ArticleViewSet(ModelViewSet):
model = Article
@action(detail=False, methods=["post"])
def list_articles(self, request, *args, **kwargs):
return self._list(request, *args, **kwargs)
Features
- Multi-database routing (
use_db) built into every model without extra wiring. - Recursive bulk-attribute assignment (
Model.set()) that also creates, updates or deletes nested one-to-one/one-to-many child objects in one atomic transaction. - A
ModelViewSetcontract that standardizes the error envelope, returns 204 (not 200 with an empty body) for an empty list, and centralizes exception-to- response translation. - A SimpleJWT-aware
CurrentUserDefaultandget_user()helper, so serializer defaults and permission checks work whetherrequest.useris a real model instance or a lightweightTokenUser. FileUtils, backed by Django's storage API, for reading/saving/inspecting files (MIME type, checksum, size) with optional lazy feature loading.Email, built onEmailMultiAlternatives, rendering HTML/text templates and reporting partial-delivery SMTP failures instead of treating them as success.- Three ready-made REST endpoints: dial codes, languages, and timezones.
- A custom Oracle backend that lets a model field be named after a reserved word.
- The
after_ready-deferred signal-wiring pattern (ZbAppConfig), shared by every optional package's ownAppConfig, so model/DB access during signal connection never races Django's app-loading sequence. - A
MinimumLevelRequired(max_level=N)permission, granting access by privilege level (get_user_level(), lower integer = higher privilege) with a superuser bypass and an explicit 403 (code="insufficient_level") instead of a plainFalsewhen the level can't be resolved or falls short. - No optional-package import at module load time anywhere in the base package:
ViewSet/ModelViewSetresolveauthentication_classesper request, sozibanu.djangostarts and serves requests with zero optional packages installed.
Usage
Basic Usage
Read the configured languages and timezones once zibanu.django.urls is mounted
under your project (see Configuration):
POST /utils/language/list/
POST /utils/timezone/list/
POST /utils/dial_codes/list/
Use the shared ErrorMessages and APIException when writing your own service
code, so errors carry this project's {"errors": [...], "code": ...} envelope:
from zibanu.django.rest_framework.exceptions import APIException
from zibanu.django.lib.utils import ErrorMessages
raise APIException(ErrorMessages.NOT_FOUND, code="does_not_exists", status_code=404)
Advanced Usage
Send a templated e-mail, resolving the current request automatically for template context and reporting SMTP partial-delivery failures:
from zibanu.django.lib.utils import Email
email = Email(subject="Welcome", to=["user@example.com"])
email.set_html_template("emails/welcome", context={"user_name": "Jane"})
email.send()
Inspect an uploaded file without loading it eagerly, then persist it through Django's storage API:
from zibanu.django.lib.utils import FileUtils
fu = FileUtils(
file_dir="uploads", file_name="avatar.png",
file_handler=uploaded_file, overwrite=True, eager_load=False,
)
fu.save()
print(fu.file_size, fu.mime_type)
Restrict a plain function-based or class-based view action with the project's
permission decorator (note it must go through method_decorator on a class-based
view — see the decorator's own docstring for why):
from django.utils.decorators import method_decorator
from zibanu.django.rest_framework.decorators import permission_required
class ReportViewSet(ViewSet):
@method_decorator(permission_required(("is_staff", "reports.view_report")))
def list(self, request, *args, **kwargs):
...
Gate a view by privilege level instead of a fixed permission string — grants
access when the resolved level is at or above the threshold
(level <= max_level), or when the user is a superuser:
from rest_framework.permissions import IsAuthenticated
from zibanu.django.rest_framework.permissions import MinimumLevelRequired
class InternalReportViewSet(ViewSet):
permission_classes = [IsAuthenticated, MinimumLevelRequired(max_level=2)]
API Reference
zibanu.django.db.models
Model— abstract base adding theuse_dbrouting attribute andset(attributes, force_update=True), a transactional bulk-assign that also walks and persists related one-to-one/one-to-many child objects.DatedModel— extendsModel, addingcreated_at/modified_at(auto_now_add/auto_now).Manager— routesget_queryset()by the model'suse_db, and addsget_by_pk(pk), raisingModel.DoesNotExistwhen no row matches.PhoneField— aJSONFieldstoring{"dial_code": ..., "phone_number": ...}, validated againstDialCodes.
zibanu.django.rest_framework.viewsets
ViewSet—rest_framework.viewsets.ViewSetdefaulting toIsAuthenticated.authentication_classesis empty at class-body scope;get_authenticators()resolves it per request, falling back to[JWTTokenUserAuthentication]only whenzibanu.django.authis installed (and appendingTokenAuthenticationwhensettings.DEBUGisTrue) — no optional-package import happens at module level.ModelViewSet—rest_framework.viewsets.ModelViewSetrestricted toPOST(http_method_names = ["post"]); the publiclist/retrieve/create/update/destroyall raiseNotImplementedException(405). A concrete subclass exposes its own@action-decorated POST endpoints calling the protected_list/_retrieve/_create/_update/_destroy/_destroy_listhelpers, which translate ORM/validation errors intoAPIException. Same deferredauthentication_classesresolution asViewSet.
zibanu.django.rest_framework.decorators
permission_required(permissions, raise_exception=True)— validates Django permission strings (plus the special"is_staff"flag) against the resolved current user; raisesPermissionDeniedby default.
zibanu.django.rest_framework.exceptions
APIException— base exception; buildsdetail = {"errors": [...], "code": ...}and defaults message/code from the resolved HTTP status.NotAuthenticated,PermissionDenied,NotImplementedException,MultipleLoginError— project-specific subclasses;ValidationError,NotFound,ParseError, etc. are re-exported from DRF unchanged.
zibanu.django.rest_framework.fields
CurrentUserDefault— resolvesrequest.userthroughget_user(), so it works whetherrequest.useris a real model instance or a SimpleJWTTokenUser.HybridImageField— accepts base64 or multipart uploads, validates width/ height againstimage_width/image_heightcontext keys, and can resize the base64 representation viaimage_size.
zibanu.django.rest_framework.permissions
IsOwnerOrReadOnly— allows safe methods to everyone; write methods only whenobj.owner(resolved viaget_user()) matches the requesting user.MinimumLevelRequired(max_level)— instantiated per view (not a bare class inpermission_classes); grants access to a superuser unconditionally, otherwise whenget_user_level()resolves anintat or above the threshold (level <= max_level). Raiseszibanu.django.rest_framework.exceptions.PermissionDeniedwithcode="insufficient_level"— never a plainFalse— when the level can't be resolved or falls short. Implements__call__returningselfso DRF'sget_permissions()(which otherwise expects to instantiate each entry itself) accepts an already-built instance.
zibanu.django.lib.classes
BaseModelController— wraps load/save access to a model instance and its related child objects, driven by aMetainner class validated throughModelControllerMetaClass.CodeGenerator— generates numeric/alphanumeric/secure codes and UUIDs for verification/short-lived-code use cases.ExtendedJSONEncoder— aDjangoJSONEncodercoveringFieldFile, plain dates, model instances, and any duck-typedgeojson-carrying geometry object.ModelName— astrsubclass parsing anapp_label.ModelNameidentifier.RegEx— a regular-expression pattern builder plus character-class constants (LETTERS,DIGITS,PHONE_NUMBER,TAX_ID,MAC_ADDRESS, ...).
zibanu.django.lib.enums
DialCodes— aTextChoicesenumeration of supported country dial codes, withdial_codes_list()andsorted(alphabetic=True)helpers.
zibanu.django.lib.utils
Email—EmailMultiAlternativessubclass rendering HTML/text templates and reporting partial-delivery SMTP failures viaPartialSendError.FileUtils— file operations backed by Django's storage API (metadata, checksum, save/read/delete), with optional lazy feature loading.ErrorMessages— reusable, translatable error-message constants.change_timezone(date, tz=None)/add_timezone(date, tz=None)— relabel or apply a timezone to adatetimevalue.get_user(user)/get_user_object(user)— resolve a real Django user (thezibanu.django.authproxy when installed) from a SimpleJWTTokenUser.get_user_level(user) -> int | None— resolvesuser's privilege level, always reading it fresh from the database (never a cached JWT claim): triesget_user(user)'s ownlevelattribute first, then falls back to a direct query againstzibanu.django.auth.models.Userwhen that package is installed and the attribute was absent (covers theTokenAuthenticationdebug path). Never raises — any resolution failure returnsNone.get_model_list()/get_model_choices()/get_model_fields()— introspect installed Django apps for concrete models.get_ip_address(request)/get_http_origin(request, md5=False)— HTTP request helpers.get_receiver_id(**kwargs)/get_sender_name(sender)— signal-receiver helpers.object_to_list(obj)/import_class(dotted_name)— flatten a nested object into human-readable strings; import a symbol by dotted path.
zibanu.django.api.services
DialCodesViewSet,LanguageViewSet,TimeZoneViewSet—POSTendpoints listing dial codes, Django's configured languages, and available timezones, mounted byzibanu.django.urls.
zibanu.django.app_config
ZbAppConfig— baseAppConfigdeferring signal wiring to anafter_readybackground thread, so model/DB access never races Django's app-loading sequence. Subclassed by every optional package's ownAppConfig, never instantiated directly (default = False).
Configuration
Wire the base package's pieces into your Django settings:
MIDDLEWARE = [
# ...
"zibanu.django.lib.middleware.RequestContextMiddleware",
]
REST_FRAMEWORK = {
"EXCEPTION_HANDLER": "zibanu.django.rest_framework.utils.exception_handler",
# ...
}
Mount the base package's own REST endpoints:
urlpatterns = [
path("utils/", include("zibanu.django.urls")),
]
Relevant settings:
| Setting | Purpose |
|---|---|
ZB_MAIL_DEFAULT_FROM |
Default From address for Email. Falls back to DEFAULT_FROM_EMAIL when unset (ZbDjango.ready() logs when it is). |
ZB_MAIL_REPLY_TO |
Default Reply-To address for Email, when set. |
Testing
python manage.py test zibanu.django.tests
This package's own tests live under zibanu/django/tests/, including the shared
test infrastructure (test_base.py's BaseTestCase, fixtures, fixture_path())
that the optional packages' own test suites import from.
Requirements
- Python 3.9+
- Django >= 3.2
- djangorestframework
- djangorestframework-simplejwt
- Pillow
- drf-extra-fields >= 3.7
djangorestframework-simplejwt is pinned in the installer's own pyproject.toml,
but the base package never imports it at module level — ViewSet/ModelViewSet
import it lazily, per request, only once zibanu.django.auth is installed (see
Features). A deployment running zibanu.django with no optional
package can omit it in practice, even though the installer still declares it.
Also required at runtime by specific modules, though not declared in the package's
own pyproject.toml: filetype (FileUtils's MIME-type detection) and
timezone-utils (TimeZoneViewSet's timezone choices).
License
GPL-3.0-or-later. See the package's own installers/zibanu-django/LICENSE.
Changelog
All notable changes to zibanu.django (the base package) are documented here, one
section per principal version (x.y.z). Pre-releases of the same version
(x.y.z-dev.N, -alpha.N, -beta.N) fold into that version's section rather than
getting a heading of their own.
Scope: this file covers zibanu/django/api, db, lib, rest_framework, locale,
static, and the package root (__init__.py, apps.py, urls.py) — i.e. the code
that ships as the base package. Changes confined to an optional package's own
directory (auth/, repository/, logging/, template/, gis/, mqtt/,
state_machine/) are out of scope and live in that package's own CHANGELOG.md.
[3.0.0] — unreleased
Added
zibanu.django.app_config.ZbAppConfig— sharedAppConfigbase class centralizing theready()/after_ready()deferred-signal-wiring pattern (event-wait with a bounded polling fallback); adopted byzibanu.django.repositoryand available for the other optional packages to share the same guard instead of each reimplementing it.zibanu.django.lib.middleware.RequestContextMiddleware— publishes the active request on acontextvars.ContextVarsoget_request_from_stack()resolves it in O(1); the original call-stack walk remains as the fallback when the middleware isn't installed in a consuming project.Email(lib/utils/mail.py) now tolerates a partial SMTP send: a newPartialSendErrorexception and dedicated logging/signal path report exactly which recipients were refused, distinct from a full send failure.get_user_level()(lib/utils/user_utils.py) — a new public helper resolving a user's privilege level: it readslevelduck-typed off the translated user and, when that is absent, falls back to a direct database lookup by primary key against thezibanu.django.authUsermodel, only if that optional package is installed. It never raises and never caches — any resolution failure (missing row,zibanu.django.authnot installed, a database error) is reported asNonerather than an exception or a stale value. (ZBDJ-759)MinimumLevelRequired(rest_framework/permissions/) — a new view-level permission class, instantiated per view with amax_levelthreshold (permission_classes = [IsAuthenticated, MinimumLevelRequired(max_level=N)]). It grants access throughget_user_level()(with anis_superuserbypass that never calls it) and denies explicitly withPermissionDenied(code="insufficient_level")rather than a plainFalse; it also implements__call__(returning itself) so DRF'sget_permissions()accepts the pre-instantiated form shown above without raisingTypeErrorbeforehas_permissionis ever reached. (ZBDJ-768, ZBDJ-772)
Changed
- Breaking:
PermissionDeniednow answers HTTP 403 (Forbidden) instead of 401 (Unauthorized), matching the docstrings and OpenAPI spec that already documented 403 forpermission_requiredrejections. APIException's default errorcodeis now derived from the response's status code through an internal status→code mapping when no explicit code is supplied, instead of a single fixed default — an uncontrolled 500 now answers a consistent code regardless of call site.CurrentUserDefault.get_default()now resolves the current user fromrequest.user(it was previously handing the wholerequestobject to the user-resolution helper) and raises instead of silently returningNonewhen the serializer context carries no request.IsOwnerOrReadOnlynow compares the object owner's primary key against the resolved local user instead ofrequest.userdirectly — under this package's JWT authentication,request.useris a SimpleJWTTokenUser, which never compares equal to a real user model instance.ViewSet/ModelViewSetbuild their authenticator list per request, inget_authenticators(), instead of freezing it as a class attribute evaluated once when the module is first imported —settings.DEBUGis now honored live instead of at process start.ModelViewSet.create/.updatenow call the serializer'ssave()instead of callingcreate()/update()directly, so any pre/post-save hook defined on the serializer actually runs.ModelViewSet.get_querysetraisesImproperlyConfiguredwhen a subclass doesn't definemodel, instead of failing later with an unrelatedAttributeError.Model.save()/.delete()now honor the model'suse_dbrouting attribute;save()no longer forwards a stray positional*argsinto Django's ownsave().Model.set(): one-to-many child reconciliation now deletes rows absent from the payload with a singleexclude(...).delete()instead of one.delete()per row; child-identity comparisons use.pkinstead of.id, so it now works for a child model without Django's defaultidprimary key; callingset()with no attributes at all now raises immediately instead of failing further down with an unrelated error.Manager.get_by_pk()checks existence with.exists()instead of materializing the full matching queryset.PhoneFieldno longer caches a row'sdial_code/phone_numberon the shared field instance (aFieldis one object shared across every model instance, request and thread);validate()now validates a merged copy instead of mutating the caller's dictionary; the field gaineddeconstruct()so a non-defaultdial_coderound-trips through migrations.CodeGenerator.get_alpha_numeric_code()/get_secure_code()now guarantee one character from each declared character class via a fixed-alphabet draw-and-shuffle, replacing an implementation whose effective alphabet grew from its own partially-generated output as it ran — so the advertised guarantee didn't actually hold.ExtendedJSONEncoder: a nested model value is now returned as adictfor the outer encoder to recurse into, instead of being pre-serialized to a JSON string that ended up double-encoded inside the payload; anyFieldFile(not justFileField/ImageFieldFile) is now recognized; adatetimeformatting bug that used%M(minutes) instead of%m(month) is corrected.object_to_list()no longer raisesTypeErroron a numeric error-detail value (it tested emptiness withlen()), and no longer silently drops a non-string, non-proxy scalar value — a redundantisinstance(value, object)check had made an entire branch permanently unreachable.get_sender_name()(lib/utils/receivers_utils.py) carried the identical always-trueisinstance(value, object)mistake, also fixed.ModelControllerMetaClassraisesImproperlyConfiguredinstead of relying onassert(stripped when Python runs with-O) for its configuration checks, requires aMetaclass to be defined at all, and now copies the inheritedMetabefore mutating it, so a default it sets for one controller no longer leaks onto sibling subclasses sharing the same baseMeta.BaseModelControllernow actually assigns the related-object list it builds while loadingkwargsonto the instance — previously computed and discarded, making that whole walk dead work; it copies the incomingkwargsdict instead of mutating the caller's own, and narrows its blanketexcept ExceptiontoValidationError/ValueError, so a genuine programming error now propagates instead of being reported to the client as invalid input.HybridImageField: builds its guessed MIME type fromfile.nameinstead offile.path, which raises on any non-filesystem storage backend; width/height limits supplied via serializer context are now honored independently of each other instead of requiring both keys present at once to apply either.get_user()(lib/utils/user_utils.py) now importsrest_framework_simplejwtlazily inside the function body, so importing this helper no longer requires SimpleJWT to be installed when the caller never needed theTokenUsertranslation.MultipleLoginErrornow carries a default detail message ("A session is already open for this user.") instead ofNone.DialCodesViewSet.listno longer inverts itssortedrequest flag;LanguageViewSet.listreturns a{code, name}list built fromsettings.LANGUAGESinstead of an always-empty placeholder;TimeZoneViewSet.list's "no results" check now tests the actual timezone list instead of the always-non-empty wrapper dict around it.ZbDjango.ready()no longer writes computed defaults forZB_MAIL_DEFAULT_FROM/ZB_MAIL_REPLY_TOback onto thesettingsobject; it now only logs whenZB_MAIL_DEFAULT_FROMis left unconfigured.Email'sfrom_email/reply_tosettings are now read withgetattr(...)instead of direct attribute access, so sending no longer raises whenZB_MAIL_DEFAULT_FROM/ZB_MAIL_REPLY_TOaren't configured; when noZB_MAIL_REPLY_TOis set, noReply-Toheader is built at all (previously it went out as the literal string"None").urls.py: route names are nowdial_codes_list/timezone_list/language_list(were free-text, human-readable strings), and the module declaresapp_name = "zb_django"for namespaced URL reversal.ModelViewSet/ViewSet.authentication_classesnow default to an empty list instead of hardcoding[JWTTokenUserAuthentication]; the JWT authenticator is resolved lazily byget_authenticators(), which importsrest_framework_simplejwtonly whenzibanu.django.authis installed.lib/__init__.pynow imports.enumsbefore.classes, breaking an import cycle (.classes→zibanu.django.db.models.Model→PhoneField→ back to.enums.DialCodes) that only surfaced once nothing else had already forced this package to fully initialize first. Net effect: the base package now starts correctly with no optional package installed, where it previously requiredzibanu.django.auth/rest_framework_simplejwtto be present at import time regardless of whether a given deployment used JWT auth at all. (ZBDJ-752)
Fixed
get_request_from_stack()(used by audit logging on every model save/delete) now walks the call stack withsys._getframe()and stops at the firstget_responseframe, instead of building a fullinspect.stack()list — which resolves source location for every frame — on every call; it also reads the request viaf_locals.get()so a frame without that local no longer raisesKeyError. It now also checks theRequestContextMiddlewarecontext var first (see Added, above).ExtendedJSONEncoder.default()now serializes any value exposing ageojsonattribute (duck-typed) instead of checkingisinstance(obj, GEOSGeometry), so the base package keeps no import-time dependency ondjango.contrib.gis(GDAL/GEOS) — installations withoutzibanu.django.gisare unaffected either way.FileUtils.file_pathnow goes throughFileSystemStorage.path()(which composes viasafe_join), so a traversing file name raisesSuspiciousFileOperationinstead of resolving to a path outside the storage root.FileUtils.delete()now returnsFalse, notTrue, when there was nothing to delete — the previous return value contradicted its own documented contract.FileUtils.save()adopts the name the storage backend actually wrote (which can differ from the requested name once the backend de-duplicates it), so a later read/delete targets the file that was actually written.FileUtils's extension/MIME detection no longer rewrites a file name to a bare trailing dot when detection produces no extension, and no longer raises on a handler with nonameattribute.add_timezone()(lib/utils/date_time.py) now callsdjango.utils.timezone.make_aware()instead of atz.make_aware()method thatZoneInfodoesn't provide.ModelName.app_label/.model_namenow raise a clearValueErrorfor a malformed identifier instead of anIndexErrorfrom the unguarded split.RegEx's custom-alphabet pattern builder no longer contains an always-taken dead branch that, in one code path, left a custom pattern unbuilt.
Note: in this range, Email (lib/utils/mail.py) went through an intermediate
revision that resolved the active request lazily at render time instead of capturing it
in __init__ (closing a locmem-backend pickling failure on the stored request). A
later, larger rewrite of the same file — adding the partial-send handling described
above — reintroduced the eager self.__request = get_request_from_stack() capture in
__init__, which is the form this range ends on.
Note: a 2.1.3-dev.0 bump (no functional change — an __init__.py docstring
tweak) briefly preceded the jump straight to 3.0.0-dev.0; 2.1.3 was never a real
development line.
[2.1.2] — 2026-08-13
Fixed
- Fixed a temporary-file leak in
FileUtils.__load_features: thefile_contentcode path createdNamedTemporaryFile(delete=False), leaving orphaned files under/tmp. It now usesdelete=True(every consumer reads from the already-open stream, never the path), flushes the file after writing, and computesfile_sizefromlen(file_content)— also fixing a0-byte size that was previously reported from the unflushed buffer.
Changed
__build__is now tracked as its own field indjango/__init__.py, independent of__version__.
[2.1.1] — 2026-05-15
Added
- The
zb_context_datacontext processor gainedfull_uri,full_static_uri,full_media_uri,site_name,site_logo,site_favicon,site_descriptionandsite_version;Email(lib/utils/mail.py) now reads the current request off the stack to build them. (Thenew_user,send_codeandrequest_passwordmail templates were updated accordingly — see theauthpackage's own changelog.)
Changed
- Removed the
force_extensionflag fromFileUtils.
[2.1.0] — 2026-03-07
Added
ModelViewSetcan now accept a pre-builtquerysetvia**kwargs, bypassing its ownget_queryset()— added to let services (e.g. a role-aware menu listing) run a custom filter instead of the default one.lib.utils.model_utils.get_model_choices()— a companion toget_model_list()that returns a model-name choices tuple, with an optional empty entry, for admin/form selects.RegEx(lib/classes/regex.py) extended with more building blocks for composing regular expressions.- A shared Django-admin static asset: a "tabular inline scroll" widget
(
static/zb_django/{css,js}/zb_tabular_inline_scroll.{css,js}) for inline admin forms.
Fixed
- Corrected the 404 error code for a missing object from the misspelled
doest_not_existstodoes_not_exists. FileUtilsreceived extensive stabilization over this cycle — file-content handling, extension detection (full_path_filename, andfull_file_namerenamed tofull_filename), and metadata handling — across roughly fifteen follow-up commits; net effect is a substantially more robustfile_utils.py, as consumed by the repository package's admin view and controller.
Note: during this cycle __init__.py briefly wandered to 2.0.7-dev.* before
returning to 2.1.0-dev.* — an artifact of the alpha/beta/develop branches
being merged back and forth. No 2.0.7 was ever released; the base-package changes
committed under that stray version tag are folded into this 2.1.0 section, since
they shipped as part of the same continuous development.
[2.0.6] — 2025-08-23
Fixed
- Fixed an error relocating a file in
FileUtils. (ZBDJ-275)
[2.0.5] — 2025-08-19
Fixed
- Fixed file deletion in
FileUtils. (ZBDJ-266) - Fixed a bug that allowed reassigning a PDF to an incompatible category. (ZBDJ-257)
[2.0.4] — 2025-06-19
Added
FileUtilsgained a "create version file" capability. (ZBDJ-251)
[2.0.3] — 2025-06-16
Fixed
- Corrected the
authentication_classesdefinition inmodel_viewset.pyandviewset.py. - Fixed a bug that generated an incorrect file URL when built from the file's creation date.
[2.0.2] — 2025-04-05
Added
FileUtilscan now extract a file's MIME type and, for supported categories, embedded metadata (Office: XLSX/DOCX/PPTX; OpenDocument: ODT/ODS/ODP; images: PNG/JPG/TIFF; PDF) via the newFileMetadataclass. (ZBDJ-238)
[2.0.1] — 2025-04-01
Fixed
- Fixed file-type validation in
FileUtilsthat raised an error while editing a file's admin record without attaching a new file. (ZBDJ-229/230) - Fixed the file-hash generation error in
file_utils.py.
[2.0.0] — 2025-03-14
Changed
Model.set()(base model mixin) now handles a nested one-to-one child that already has an in-memory instance but noid: it assigns the parent relation and updates it in place instead of only handling the "no instance yet" case.save()also stopped forwarding a stray*argsto Django's ownsave().
Fixed
ExtendedJSONEncoderfixed to correctly serialize nested models.ModelViewSet._updatefixed to passcontextto the serializer, which was previously omitted and broke context-dependent validation on update.
Note: a 1.5.2-dev.1 hotfix (cut 2025-02-06, the day 1.5.1 shipped, containing
the FileClass/Model.set() cleanup above) was superseded by this 2.0.0 line
before 1.5.2 was ever published as a stable version — 1.5.2 never shipped; its
one commit's changes are folded into this section instead.
[1.5.1] — 2025-02-06
Added
- New exceptions
ProtectedErrorandRestrictedError(with Spanish translations), mirroring Django's own integrity-error classes; added handling forUniqueConstraintviolations and forNotAuthenticated.
Changed
- Password / secure-code validation (
lib/classes/code_generator.py) reworked over several follow-up commits: punctuation handling corrected, minimum-length enforcement wired to app/settings configuration, and validation kept consistent between "secure" and "non-secure" password modes. (ZBDJ-154, ZBDJ-156) get_request_from_stack()now returnsNoneinstead of raising when no request is on the stack; the logging package's receivers were updated to match.- The
MAC_ADDRESSregex was tightened to accept only:as a byte separator.
Fixed
- Corrected the
.po/.motranslation build, which was silently failing to regeneratedjango.mo. - Fixed a recurring bug (three follow-up attempts before it stuck) where a dictionary-valued error message wasn't translated when converting an object to a list.
[1.5.0] — 2024-11-04
Added
BaseModelController/ModelControllermetaclass (lib/classes/base_model_controller.py,lib/metaclasses/model_controller.py) — a reusable base for building model controllers.Model.set()extended to create nested one-to-one children, to save newly created rows for nested one-to-many children, and to support nested ("Creación Anidada") and mass-delete operations. (ZBDJ-146, ZBDJ-147, ZBDJ-151, ZBDJ-152)Model.set()gained aforce_updatekeyword (defaultTrue, preserving prior behavior) so a caller can let it insert a new row instead of always forcing an update.
Changed
import_class()(lib/utils/generic_utils.py) is no longer limited to locating serializers — it can import any class by dotted path.
Deprecated
zibanu.django.utils— its contents moved tozibanu.django.lib.utils. The old import path is kept only as a backward-compatible shim; per the commit that made the move, it "will disappear in a future version."
Fixed
model_viewset.pynow forwardsmessage_dicttoAPIExceptionon update failures, instead of losing field-level validation detail.Model.set()fixed to no longer error when a related child object was created in an inconsistent state, and to support one-to-one parent-table lookups.- Several fixes to the
RegExclass powering pattern validation.
[1.4.2] — 2024-08-21
Added
ModelSerializer/ModelViewSet: the create-vs-update context is now surfaced to the serializer (acreatekey incontext, and an_addingproperty derived from it), so a serializer can tell which operation is in progress.
[1.4.1] — 2024-08-09
Added
RegEx(lib/classes/regex.py) — a new class assembling common regular-expression patterns. (ZBDJ-141)
Changed
- Secure-code/password generation (
lib/classes/code_generator.py) now limits which symbols are used and validates generated codes/passwords against that set. (ZBDJ-140)
Fixed
- Exceptions in
model_viewset.pynow surface via thedetailparameter instead oferror, aligning with the intended error-capture configuration. (ZBDJ-138) - Two follow-up fixes to
RegEx(an enum value and a validation bug) after its initial introduction.
[1.4.0] — 2024-05-10
This is the first version tracked by the package's own __version__ file, added
on 2024-05-03. The git tags 1.1.0
(2023-03-17) and 1.1.1 (2023-03-31), present earlier in this repository, predate
that tracking and don't correspond to any recorded __version__ value — see their
own sections below. Between 1.1.1 and this release (2023-04 through 2024-05, about
13 months) the base package's own surface (api/db/lib/rest_framework) has no
version marker of its own; that work is presented here by necessity, anchored on the
next real marker this history provides (the first __version__ commit).
Added
permission_requireddecorator (rest_framework/decorators.py) — validates a permission without raising, returning a boolean/response instead.zibanu.django.api: public endpoints listing available timezones and languages (api/services/timezone.py,language.py), registered inurls.py.HybridImageField(rest_framework/fields/hybrid_image.py) — image upload validation with size/format checks, introduced and iterated on repeatedly.PhoneField(db/models/fields/phone_field.py) plus a dial-code enum and theapi/services/dial_codes.pyendpoint, for phone-number fields with country dial-code lookup/validation.PermissionDeniedexception (mirroring DRF's own hierarchy) and aMultipleLoginErrorexception.ModelViewSet.perform_authenticationoverride translating SimpleJWT'sInvalidTokeninto the package's ownAPIException.
Changed
- Consolidated the
APIViewSet/ModelViewSetimplementation: unified thedetailpayload shape and status codes acrossapi_exception.py, simplifiedAPIViewSet, and movedexception_handler.pyintoutils.py. - The custom Oracle database backend (
db/backends/oracle) was hardened for reserved-word field handling.
Fixed
- Several regressions in
model_viewset.py:order_byhandling (twice), partial (PATCH) updates rejecting valid data, andto_representationreturning the wrong parameter shape. - Multiple translation/localization fixes across the
.po/.mocatalogs.
[1.1.1] — 2023-03-31
Added
CurrentUserDefaultfield (rest_framework/fields) — a "current user" default compatible with token-based authentication.
Changed
ModelViewSetnow logs through the package logger.
[1.1.0] — 2023-03-17
Added
- Initial scaffold of the base package:
Model/DatedModel/Manager(db/models), the custom Oracle database backend for reserved-word field names (db/backends/oracle), and the initialrest_framework/apps.pyskeleton. Emailutility (utils/mail.py) and mail-send audit-logging signals.
Fixed
- Corrected the sender class name and completed the Spanish translation catalog.
Metadata
Release files for zibanu-django 3.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-3.0.0.tar.gz | 117.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| zibanu_django-3.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 240.1 kB
Release files / zibanu_django-3.0.0.tar.gz
| Download URL | zibanu_django-3.0.0.tar.gz |
|---|---|
| Size | 117.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d2ec45c2e4a499016eafc984146cfaf2edade65ac75b41a66758017ed6c99e5e
|
|
BLAKE2b-256 checksum How to use checksums |
96cf7d9ec03aa9044594f4d5b496b1c1c0685c61479b3720887d211a2fa7ddc9
|
| 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-3.0.0-py3-none-any.whl
| Download URL | zibanu_django-3.0.0-py3-none-any.whl |
|---|---|
| Size | 122.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ca039f18e93f9f5544035c5ed94582766de93e0a59c6415cc5e0671bedd2d16b
|
|
BLAKE2b-256 checksum How to use checksums |
c3e4b64cbba15d67dcd279c1db5527904d3a0958ca9dad006057210b2f21df14
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.13
|