Python library
All notable changes to this project are documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[7.2.0] — 2026-10-19
Changed
- The package is published to PyPI, betas as pre-release versions, and no longer to the GitLab
PyPI registry;
eluvia-baseandeluvia-base-djangoare resolved from PyPI too. Drop the GitLab index forhabaricofrom yourPipfile.
[6.0.0] — 2026-10-10
Added
-
ModelAdmin.default_sortingand itsget_default_sorting(request)hook - the order a list endpoint answers a request that sends noorder_byin. Django-style ("placed_at"ascending,"-placed_at"descending) or aSortingdescriptor. Until nowdefault_sortingwas a per-view schema value only: the frontend read it and sent it back asorder_by, so a client that sent none - a script, another service - was answered in whatever order the model'sMeta.orderingor the database gave. AListViewSchemaGeneratorwithout adefault_sortingof its own now publishes the resource's, so the frontend and the endpoint agree on one default. This is what the Ruby gem already did. (HAB-915) -
ModelAdmin.default_page_limitandModelAdmin.max_page_limit- the page size of a resource's list endpoint and the largest one a request may ask for, per resource. The same two knobs configure every resource at once asDEFAULT_PAGE_LIMIT/MAX_PAGE_LIMITin theADMIN_REST_FRAMEWORKblock, so the admin API is paginated independently of the rest of the API; unset, both defer to the Django settings of the same names thateluvia-base-djangoapplies project wide."MAX_PAGE_LIMIT": Noneremoves the cap. Both admin attributes are checked when the admin is registered. New page: Pagination. (HAB-946) -
can_read/can_create/can_update/can_deleteaccept a method taking the request beside the bool, for per-user rules without a DRF permission class of your own.has_read_permission(request)and itshas_create_/has_update_/has_delete_counterparts are their always-callable form - named after Django's own admin permission methods- and are what the permission class asks. Where there is no request yet (registration-time
checks, the schema generators) a method counts as "may be allowed", so the action stays declared
and is settled per request; a literal
Falseis knowable there and drops it outright. Bools behave exactly as before. (HAB-948)
- and are what the permission class asks. Where there is no request yet (registration-time
checks, the schema generators) a method counts as "may be allowed", so the action stays declared
and is settled per request; a literal
-
habarico.validators.MaxFileSizeValidator- a declarative per-field upload size limit (Django and DRF ship none), attached to a file/image serializer field like any other validator:AdminFileSerializer(validators=[MaxFileSizeValidator(5 * 1024 * 1024)]). Not upload-specific - chunked uploads live entirely inhabarico_enterprise.uploads(HAB-845) - so it works the same on a plain base64 field or a chunked one.max_size_for_serializer_field()is the reader other code derives a field's declared ceiling from. (HAB-965) -
AdminFileSerializer/AdminImageFileSerializernow deriveoptions.max_size/options.acceptfrom a field's own validators and expose them to the frontend automatically -max_sizefromMaxFileSizeValidator;acceptfromFileExtensionValidator(concrete types guessed from its allowed extensions), checked first, or Django'svalidate_image_file_extension(image/*) otherwise - so anAdminImageFileSerializerfurther narrowed withFileExtensionValidator(allowed_extensions=["png", "jpg"])advertises those two types, not a blanketimage/*silently widened back out from the field simply being an image field. Neither key is emitted when it cannot be derived, and an explicitoptions={"max_size": ...}always wins over the derived one.habarico.validators.content_types_for_serializer_field()is theacceptcounterpart ofmax_size_for_serializer_field(). (HAB-966) -
Data export of the list views, in the new
habarico.exportmodule. It is off by default and a resource opts in withModelAdmin.export_enabled; there is no installation-wide switch. The synchronous export is part of the free library; the asynchronous one for bigger exports belongs tohabarico-enterprise. (HAB-405) -
habarico.exportrenderers: built-inCsvRendererandXlsxRendererbehind the new optionalexport-xlsxextra (openpyxl), both streaming into a file object and escaping spreadsheet formulas.ADMIN_REST_FRAMEWORK["EXPORT_RENDERER_CLASSES"]is the registry of offered formats — CSV alone out of the box, and addingXlsxRendererto it is what offers XLSX. (HAB-533) -
habarico.exportendpoint:ExportAdminModelViewSetservingGET <resource>/export/with the same queryset, filters and ordering as the list view, configurable throughModelAdmin'sexport_fieldsandexport_formats. Exports abovehabarico.export.views.EXPORT_ROW_LIMITrows are refused — the limit is a constant of the free library, since the answer to a bigger export is the asynchronous one ofhabarico-enterprise, not a higher ceiling on the request cycle. (HAB-532) -
habarico.exportschema generator:ExportActionDescriptorGeneratorderives the export action rendered in thelist_actionsslot of the list views from the admin's export configuration, and the newhabarico.export.configaccessors resolve the configuration for both the endpoint and the generator. Newschema.actions.get_action_namehelper reads the name of an actions-slot entry (a plain name or a descriptor). (HAB-534) -
ModelAdminwires both ends of the export in, through the helpers of the newhabarico.export.wiringmodule:get_base_viewset()mixesExportAdminModelViewSetinto the base viewset the admin already uses (a resource that does not export gets no endpoint at all), andget_view_schemas()appends the export action to thelist_actionsslot of the list views — declared generators keep their class and configuration, and an export action declared by hand wins.ModelAdmin.can_export(request)restricts the export per user: a refused request gets no export action in the schema and HTTP 404 from the endpoint, andAdminPermissionmaps the export action tocan_read, so a resource the user may not read is not exported either. Both the viewset and the action generator are resolved through theEXPORT_VIEWSET_CLASS/EXPORT_ACTION_SCHEMA_GENERATOR_CLASSsettings, which is howhabarico-enterprisereplaces them with the asynchronous export.AdminConfigurationValidatorchecksexport_fieldsandexport_formatsat registration, like the other field lists.habarico.exporthas no package-level re-exports - a name is imported from the submodule it lives in - so importinghabaricodoes not pull the export viewset and the renderers into the import graph of a project that does not export. (HAB-535) -
SchemaGenerator.get_admin()andSchemaGenerator.get_request()read whatbind()put on a generator;get_admin()raisesValueErroron an unbound generator instead of failing later with anAttributeError. (HAB-535) -
Extension discovery: when
habarico_enterprise.extensionsis importable, the classes it lists inCONTRIBUTIONSfill in theDEFAULT_*_CLASSsettings, sopip install habarico-enterpriseneeds noADMIN_REST_FRAMEWORKblock at all. Contributions rank between core's defaults and the project's own settings, so an explicit key still wins, andEXTENSION_DISCOVERY_ENABLED = Falseignores them entirely. Only the module name is known to core — no class of the extension is imported here — and anImportErrorraised inside an installed extension propagates instead of silently disabling it. (HAB-811) -
AdminAPISettings.get_instance()— returns the shared instance of aDEFAULT_*_CLASSsetting, built on first use and dropped when the settings reload.get_field_type()/get_field_format()/get_filter_type()/get_filter_format()now resolve through it (get_field_type_resolver()) instead of instantiatingDEFAULT_FIELD_TYPE_RESOLVER_CLASSper call: one/admin/<resource>/schema/request went from 88 instantiations to 1. A resolver subclass that does real work in__init__no longer pays for it on every field of every schema — but it must now be safe to share across requests. (HAB-811) -
AdminSchemaGenerator.schema_classandAdminSchemaGenerator.get_resource_schema_kwargs(), andAdminModelViewSet.get_extra_meta()— the extension points a pro/enterprise package uses to add optional keys to the resource schema and to a serialized object'smetablock. Core contributes none of them. (HAB-811) -
Extension points that let a pro/enterprise package extend admin behavior used by every resource without editing core:
DEFAULT_MODEL_ADMIN_CLASS,DEFAULT_VIEWSET_CLASS,DEFAULT_FIELD_SCHEMA_GENERATOR_CLASS,DEFAULT_ADMIN_SITE_CLASSandDEFAULT_FIELD_TYPE_RESOLVER_CLASSsettings (resolved viaADMIN_REST_FRAMEWORK, sameimport_stringpattern asDEFAULT_PERMISSION_CLASSES), plus aDEFAULT_FILE_FIELD_MIXIN_CLASSsetting replacing the hardcodedChunkedUploadFieldMixinimport inserializers/fields.pyandserializers/__init__.py(a contributed mixin answersis_pending_upload(value)for its own placeholder type). Each defaults to today's core implementation, so behavior is unchanged unless a project overrides one of these settings. (HAB-811) -
NumberFieldOptions,PhoneFieldOptionsandUploadFieldOptions, and the matchingnumber/phone/uploadslots onFieldOptions. All three were already emitted, but only as raw dicts written straight intooptionsby the serializer fields and the uploads contrib, so a hand-writtenFieldOptionscould not express them. (HAB-732) -
FilterSchema.help_text, populated from the filter, matchingFieldSchema.help_text. (HAB-732) -
dialog_widthonViewActionDescriptorParameters, settable throughActionDescriptor(view_dialog_width=...).ViewActionParametersalready had it. (HAB-732) -
FieldSchema.short_label, so a serializer field can carry the shorter label the contract defines there. It was previously reachable only through aFieldDescriptor, whose dict is merged into the field schema. (HAB-732) -
RelationFieldOptions.dialog_width, sizing the dialog a relation field opens whenlink_modeisdialog. Ruby and the JS client already had it. (HAB-732) -
Frontend validation rules —
FieldOptions.validators, an array ofValidatorRuleobjects ({type, value, message},typeone ofmax_length/min_length/max_value/min_value/regex). Rules are auto-extracted from the serializer field constraints (max_length,min_length,max_value,min_value, and the firstRegexValidatorinfield.validators;URLValidatoris skipped as its pattern is not JavaScript-compatible) and can be overridden per rule type viaoptions={"validators": [...]}on the serializer field or theField(validators=[...])descriptor; precedence is auto-extracted < explicit serializer options < descriptor. Validators are declared as a list of typedValidatorRuleobjects —MaxLengthValidatorRule,MinLengthValidatorRule,MaxValueValidatorRule,MinValueValidatorRule,RegexValidatorRule— each taking(value, message=None); subclassValidatorRule(or passtype=) to add a custom validator. Auto-extracted rules carry nomessage(the frontend renders its own localized default; backend DRF messages are not baked into the schema) — a custom message must be set explicitly via the descriptor validators. Backend validation is unaffected and still always enforced. (HAB-445) -
Fail-fast validation of an admin declaration, run once per admin class when it is registered (at app startup) via
Admin.validate_configuration(). It covers field-name lists, box/field/filter/sorting descriptors, class-type attributes, numeric constraints, and built-in CRUD action names vs.can_*flags, so a typo or a removed model field raisesImproperlyConfigured— naming the attribute, the value and a "did you mean" suggestion — instead of surfacing as an opaque traceback on the first request. The checks live inhabarico.validation.AdminConfigurationValidator; pointModelAdmin.configuration_validator_classat a subclass to add or relax them, or set it toNoneto opt out. (HAB-553)
Changed
-
BREAKING: A list endpoint orders by the primary key behind whatever else it orders by - the client's
order_by, the newdefault_sorting, or the model'sMeta.ordering- with theeluvia-base-django>=2.3.2requirement this brings in. Two records tying on the sorted column were free to swap places between two requests, which is how a paged list repeats one record and skips another; they now keep a stable position. A resource that relied on the order ties happened to come back in will see it change - that order was never defined.ordering_tiebreaker = Falseon the viewset turns it off. (HAB-915) -
The default save action (
SaveActionDescriptor, emitted as thebottom_actionsof create, edit and action views) now renders in theprimarycolour with theCheckicon instead ofsuccesswithSave, matching the frontend library's own default form-action button. (HAB-956) -
The default label of the show action was renamed from
PreviewtoDetail(and its translations updated in all twelve catalogs), so it matches the Ruby gem'sDetaillabel. (HAB-956) -
ADMIN_REST_FRAMEWORK["DEFAULT_PAGINATION_CLASS"]now defaults tohabarico.pagination.AdminModelPagination, aModelPaginationsubclass resolving the page-size limits per admin. The response shape is unchanged. A project that points the setting at a paginator of its own keeps that paginator and simply does not get the per-resource limits. (HAB-946) -
Declared license changed from
ProprietarytoMIT, matching the Ruby gem and the MIT-licensedreact-admindependency used by the frontend; aLICENSEfile was added. (HAB-842) -
ModelAdmin.viewsetis built lazily (on first access) instead of in__init__, and configuration validation now runs fromvalidate_configuration()— once per admin class at registration time — rather than on every instantiation.AdminSite.get_admin()/get_schema()build a fresh admin instance per request, so both used to be paid on every request. Readingadmin.viewsetis unchanged. (HAB-553) -
BREAKING: The Django URL namespace produced by
AdminSite.urlschanged fromeluvia-admintohabarico. Anyreverse("eluvia-admin:…")call,{% url %}tag or hard-coded namespace in a consuming app must be updated toreverse("habarico:…"). (HAB-806) -
BREAKING: Schema class names are aligned with the names used by
doc/openapi.yaml(3.0.0), so the same entity is called the same thing in the contract, the diagram and the Python code (HAB-730, HAB-708):old new BaseResourceSchemaGlobalResourceSchemaBaseViewSchemaGlobalViewSchemaBaseResourceActionSchemaGlobalResourceActionSchemaResourceSchemaLocalResourceSchemaEndpointSchemaEndpointsSchemaMenuViewParametersViewActionParametersMenuLinkParametersLinkActionParametersMenuViewEndpointParametersViewActionEndpointParametersMenuViewChoiceActionViewChoiceActionDescriptorMenuViewParametersViewActionDescriptorParametersGridItemParametersGridItemBoxParametersSortingDescriptorSortDescriptorKeyValueTypeKeyValueChoicesOptionsChoicesFieldOptionsImageOptionsImageFieldOptionsAiOptionsAiFieldOptionsRelationOptionsRelationFieldOptionsColorColorTypeFieldFormatFieldFormatTypeBase*becomesGlobal*because global/local is a domain concept (/admin/schemavs/admin/{resource_name}/schema), not an inheritance marker —Basestays reserved for the real ancestorsBaseSchemaandBaseBox, which keep their names. The parameter schemas of a slot selected by atypediscriminator now all put the discriminator first (ViewActionParameters, like the already-conformingSaveActionParameters,ListBoxParameters,ListViewParameters). The genuine menu entities (MenuSchema,MenuGroupSchema,MenuType) keep their names —ViewActionParameters/LinkActionParameters/ActionViewChoiceare shared by the menu and by actions, and stay importable fromhabarico.schema. -
BREAKING: The schema generators follow the schemas they build:
ResourceSchemaGenerator→LocalResourceSchemaGenerator,EndpointSchemaGenerator→EndpointsSchemaGenerator, and theResourceSchemaGenerator.endpoint_schema_generator_classextension point →LocalResourceSchemaGenerator.endpoints_schema_generator_class. (HAB-730) -
BREAKING: The per-resource schema response (
/admin/{resource_name}/**/schema) emits the supported methods underendpoints, notendpoint, which is what the contract has always documented. Requires the matching frontend change; Ruby sends the old key until it is aligned too. (HAB-730) -
FieldOptions.targetis now typed asLinkTargetType(self/blank) instead of a barestr. The value is stored without the leading underscore —blank, not_blank— and the client prepends it when rendering, matchingLinkActionParameters.target. (HAB-708) -
BREAKING: The box read-only flag is called
read_onlyeverywhere instead ofis_readonly— theBaseBoxkeyword argument, theget_read_only()getter, theSchemaContextproperty and the emitted schema key. The contract was renamed toread_onlyin the same batch so that the flag is spelled the same way asFieldSchema.read_onlyandFieldDescriptor.read_only. Host apps declaring boxes withis_readonly=Truemust switch toread_only=True. Requires the matching frontend change. (HAB-732, HAB-708) -
BREAKING:
FieldOptionsemits the number formatting options under thenumberkey instead ofnumber_format, completing the rename the contract made in HAB-705. The key is written straight into the untypedoptionsdict byAdminDecimalField,AdminIntegerField,AdminFloatFieldand the aggregate field generator, so it was missed when the schema classes were aligned. Requires the matching frontend change. (HAB-732, HAB-708)
Removed
-
The stale
Enter an ID card in the format XXXXXXXXX.catalog entry — no code raises it; the CZ ID card validator's only message isEnter a valid ID card number.(HAB-907) -
The 49 catalog entries that belong to
habarico-enterprise(Search with AI,AI Assistant, the aggregation, upload, change-history and CZ localflavor messages). They were left here by the monorepo split: the paid package was not a Django app, so it had nowhere to keep a catalog and its strings were translated in this free package instead. It ships its ownlocale/from HAB-907 onwards, and a project that upgrades this package must upgrade it too — and addhabarico_enterprisetoINSTALLED_APPS— or those strings fall back to English. Wording this package uses itself (Yes,No) is untouched. (HAB-907) -
BREAKING:
DEFAULT_UPLOAD_FIELD_MIXIN_CLASSESis renamed toDEFAULT_FILE_FIELD_MIXIN_CLASSand takes one class (orNone) instead of a list. The mixin is spliced intoAdminFileSerializer/AdminImageFileSerializer, so it is named after what it attaches to rather than after the feature that uses it — core has no notion of uploads. An extension that needs several mixins composes them into one class first, exactly as it already does forModelAdmin. (HAB-811) -
BREAKING: The
DEFAULT_UPLOAD_REFERENCE_CLASSESsetting is gone. Core no longer compares a validated value against a configured placeholder type - it asks the field instead, throughis_pending_upload(value)on the mixin contributed viaDEFAULT_UPLOAD_FIELD_MIXIN_CLASSES. The mixin owns its placeholder class, so the second setting only restated what the first one already implied. A project that set it can simply delete the key. (HAB-811) -
BREAKING: The free library no longer emits the pro/enterprise resource-schema keys
global_fulltext_search_enabled,aggregate_enabledanddashboard_enabled. They were written on every resource asfalseeven without the features installed; all three are optional incontract/openapi.yaml, so a free-only install now omits them.GlobalResourceSchemano longer accepts them —habarico_enterprisedeclares them on its own subclass. Frontends must treat them as optional (@eluvia/habaricoalready does). (HAB-811) -
BREAKING: Match highlighting is gone from core:
ModelAdmin.search_highlighter_class/get_search_highlighter()andAdminModelViewSet.get_meta_highlights()no longer exist, and the list endpoint's optionalmeta.highlightskey is contributed byhabarico_enterprisethroughAdminModelViewSet.get_extra_meta(). Per-resource search (search_fields/SearchFilter) is unaffected. (HAB-811) -
validate_constraints()no longer checksaggregate_max_buckets, andvalidate_configuration()no longer probes for avalidate_aggregate_fields()hook. Both checks moved tohabarico_enterprise.validation.AdminConfigurationValidator, which owns the attributes they validate;validate_constraints()stays as the extension point a validator subclass overrides. (HAB-811) -
BREAKING: CZ localflavor moved out of the free library into
habarico_enterpriseper the open-core boundary decided in HAB-798.habarico.models.localflavor.czandhabarico.validators.localflavor.cz(CzechIcoField,CzechDicField,CzechBirthNumberField,CzechBankAccountNumber,CzechIdCardNumberFieldand their validators) no longer exist in the free package — import them fromhabarico_enterprise.models.localflavor.cz/habarico_enterprise.validators.localflavor.czinstead. (HAB-845) -
BREAKING: AI integration (
contrib/ai) moved out of the free library intohabarico_enterpriseper the open-core boundary decided in HAB-798.habarico.contrib.aino longer exists — importAIModelAdmin,PredictiveCharField/PredictiveTextField,AIAdminModelSerializer,AIAdminModelViewSet,AIAdminFilterSetand the AI client fromhabarico_enterprise.aiinstead. Theaiextra (openai,pyparsing) is removed frompyproject.toml— the free install no longer pulls in either. (HAB-845) -
BREAKING: Change history (
contrib/reversion) moved out of the free library intohabarico_enterpriseper the open-core boundary decided in HAB-798.habarico.contrib.reversionno longer exists — importVersionAdmin,RevisionAdmin,VersionListBox,VersionSerializerandVersionFilterSetfromhabarico_enterprise.reversioninstead. Thereversionextra (django-reversion,dmp) is removed frompyproject.toml— the free install no longer pulls in either.history_fieldsonModelAdminwas already a plain duck-typed attribute (no core declaration) and is unaffected — it only has meaning oncehabarico_enterprise.reversionreads it. (HAB-845) -
BREAKING: Chunked/S3/tus uploads (
contrib/uploads) moved out of the free library intohabarico_enterpriseper the open-core boundary decided in HAB-798.habarico.contrib.uploadsno longer exists — installhabarico-enterpriseand pointDEFAULT_ADMIN_SITE_CLASS/DEFAULT_FIELD_SCHEMA_GENERATOR_CLASS/DEFAULT_UPLOAD_FIELD_MIXIN_CLASSES/DEFAULT_UPLOAD_REFERENCE_CLASSESathabarico_enterprise.site.AdminSite/habarico_enterprise.schema.FieldSchemaGenerator/habarico_enterprise.uploads.fields(seedocs/python/chunked-uploads/installation.md). Theuploads-s3/uploads-tusextras (boto3,django-storages) are removed frompyproject.toml— the free install no longer pulls in either, and base64 file/image uploads (AdminFileSerializer/AdminImageFileSerializer) are unaffected since they stay in the free library. (HAB-845) -
BREAKING: The
/aggregateendpoint and the dashboard opt-in moved out of the free library intohabarico_enterpriseper the open-core boundary decided in HAB-798.FieldAggregationis no longer exported fromhabarico— import it fromhabarico_enterpriseinstead, andAggregateFunctionType/GroupByTypefromhabarico_enterprise.aggregations.schema(previouslyhabarico.schema.aggregations, which no longer exists).ModelAdmin.aggregate_enabled/aggregate_fields/aggregate_max_bucketsanddashboard_enabledare no longer core attributes — they're contributed byhabarico_enterprise.admin.ModelAdmin(AggregateAdminMixin) viaDEFAULT_MODEL_ADMIN_CLASS, and the/aggregateendpoint byhabarico_enterprise.views.AdminModelViewSet(AggregateViewSetMixin) viaDEFAULT_VIEWSET_CLASS.GlobalResourceSchema.aggregate_enabled/dashboard_enabledstay in the free schema vocabulary — a free-only resource simply always reports both asfalse. (HAB-845) -
BREAKING: The cross-resource global search endpoint and match highlighting moved out of the free library into
habarico_enterpriseper the open-core boundary decided in HAB-798.habarico.search(SearchHighlighter) no longer exists — it lives athabarico_enterprise.search.highlighter.SearchHighlighter.AdminSite.get_search_result()/get_search_view()and the/admin/search/route (AdminSearchView) are no longer in core — they're contributed byhabarico_enterprise.site.AdminSite(SearchAdminSiteMixin) viaDEFAULT_ADMIN_SITE_CLASS.ModelAdmin.global_fulltext_search_enabled,get_search_result()/get_search_object_data()/get_search_title()/get_search_description()andsearch_schema_generator_classare no longer core attributes/methods — they're contributed byhabarico_enterprise.admin.ModelAdmin(SearchAdminMixin) viaDEFAULT_MODEL_ADMIN_CLASS. Per-resource list search (search_fields,?search=…) is unaffected and stays free — only themeta.highlightssidecar on both the list endpoint and the (now enterprise-only) global search endpoint requireshabarico-enterprise:ModelAdmin.search_highlighter_classdefaults toNonein core, soAdminModelViewSet.get_meta_highlights()returnsNonewithout it.GlobalResourceSchema.global_fulltext_search_enabledstays in the free schema vocabulary — a free-only resource simply always reports it asfalse. (HAB-845) -
BREAKING:
ResourceActionParameters.reference. It was set from the same variable as the nestedResourceActionEndpointParameters.reference, so the resource action carried the identical value twice. Only the nested one is described by the contract and read by the frontend (schema.resource?.endpoint?.reference); the outer copy was not in the JS type at all. (HAB-732)
Fixed
-
default_sortingis no longer refused at registration when it names an annotation rather than a model field. Where an annotation comes from is not knowable then - it may sit on the viewset's queryset, be added in a customget_queryset(), or be attached by the ordering filter itself (OrderingFilterannotates<field>__choice_labelinside_order_queryset, and any lookup class may do the same fromupdate_queryset) - so the check refused configurations that work. It is gone, for the resource-leveldefault_sortingand for a view generator's alike; a typo surfaces on the first list request as a DjangoFieldErrornaming the keyword.ordering_fieldsis still checked, now against the lookup of a(key, lookup, label)triple rather than the client-facing key, and tolerating annotations declared on the viewset's queryset and anything in itsextra_ordering_fields. (HAB-915) -
A list accepts the ordering its own schema asks the frontend for: the
default_sortinga list view publishes, and the primary key, which is whatserializeOrderBysends for a list with no sorting of its own. An admin narrowingordering_fieldsrefused both with422 Unknown field name.- on the first load of the list, before the user had touched anything.ordering_fieldsstill bounds what a client may choose. (HAB-915) -
An unconfigured project no longer needs the
DEFAULT_PAGE_LIMITDjango setting. Its first admin list request used to fail withAttributeError; the page size now falls back to 20 - the same default the Ruby gem paginates by - througheluvia-base-django2.3.1. (HAB-946) -
AppDensityPreferenceacceptsstandardinstead ofnormalas the middle UI density, matching the value the frontend has sent sincenode@6.7.0.PUTonpreferences.layout.app.densityanswered422for the only value the frontend produces; anormalstored by an older frontend now fails validation on the next read, is deleted, and the registered default takes its place. (HAB-939) -
The
Exportaction label was the one translatable string in this package with no catalog entry at all, so the button readExportin every language. Translated in all twelve, in the verb form each catalog already uses for an action label. (HAB-907) -
Admin configuration validation no longer rejects a named
FieldWrapper: the wrapper'snameis presentational and never backs a model/serializer field, so only its sub-fields are resolved against the model. Previously a valid named wrapper raisedImproperlyConfiguredat registration, and a typo in a sub-field was masked by the wrapper-name error. -
The field schema exported
"sortable": []instead offalsewhenever a view had no sortable fields.FieldSchemaGeneratorbuilt the flag withself._sortable_fields and …, so an empty list short-circuited to that list rather than toFalse, and onlyNone/emptyare stripped from the serialized schema. Because_get_view_sortable_fields()returns[]for a detail object, for any non-GET method and for a view without an ordering backend, practically every non-list endpoint was affected — and the frontend evaluates the flag as!!sortable, where!![]istrue, so columns stayed sortable even when the backend could not sort by them. The flag is now always a boolean, and it is also emitted (asfalse) when no sortable fields are passed at all, ascontract/openapi.yamlmarkssortablerequired onFieldSchema. (HAB-773) -
docs/python/aggregations/extending-aggregate-functions.mdadvised subclassingAggregateFunctionType/GroupByType, which Python does not allow for anEnumwith existing members (TypeError: ... cannot be extended). The page now states plainly that this isn't currently supported instead of giving broken advice. (HAB-811) -
habarico.ModelAdminandhabarico.sitewere resolved eagerly, as module-level assignments evaluated the moment Django imports thehabaricopackage while iteratingINSTALLED_APPS(duringAppConfig.create(), beforeapps.apps_readyis set). Withhabarico-enterpriseinstalled,DEFAULT_MODEL_ADMIN_CLASSresolving tohabarico_enterprise.admin.ModelAdminpulled in DRF's schema machinery at that point and crashed every management command / server start withAppRegistryNotReady.ModelAdminis now resolved lazily, on first attribute access;sitenow defers building itsDEFAULT_ADMIN_SITE_CLASSsingleton the same way (wrapped indjango.utils.functional.SimpleLazyObject, so it still behaves as one shared instance once built). No public API change. (HAB-852) -
The default
editandshowactions carried noicon, so their buttons rendered without the icon the Ruby library sends for the same actions (Edit/Visibility) —createanddeletealready had theirs. Both generators now set it, and all four default actions are covered by a test. (HAB-825) -
ActionSchema(type=ActionType.RESOURCE, ...)raisedTypeError: ResourceActionParameters.__init__() got an unexpected keyword argument 'path'. The branch still passedpath,methodandrecord_pkflat, from before those moved intoResourceActionEndpointParameters, and never passed anendpointat all — so a resource action built throughActionDescriptor(action=ActionSchema(...))could not be constructed. The sibling branch inResourceActionSchemawas already correct; the two now match and the case is covered by a test. (HAB-732) -
README documented the
AdminFileSerializertargetoption with the old HTML anchor values (_blank,_self,_parent,_top), contradicting theLinkTargetTypetyping onFieldOptions.target; the examples now useblank/self. (HAB-732) -
Replaced the unmaintained
dmpdependency inhabarico.contrib.reversionwith the maintaineddiff-match-patchpackage to remove aSyntaxWarning: invalid escape sequenceon import. (HAB-893)
Security
-
BREAKING: The
limitquery parameter of the list endpoints is now validated instead of being silently corrected: one above the maximum, a non-numeric one and a negative one are rejected with422 Unprocessable Entity, with the error attached to thelimitfield. Until now an oversizedlimitwas truncated to1000and an unreadable one replaced by the default, so a client asking for?limit=5000was quietly served a page of 1000. A missing or emptylimitstill falls back to the default page size, andlimit=0remains a deliberate meta-only request. Comes with theeluvia-base-django>=2.3.1requirement. (HAB-946) -
A
can_read/can_create/can_update/can_deletedeclared as a method was silently ignored.AdminPermissionread the declaration as a plain truthy value, and a bound method is always truthy, so an admin writingdef can_read(self, request): return request.user.is_staffin the belief it was introducing a per-user rule gotlistandretrieveopen to everyone - the method was never called. Declarations are now resolved through the admin, so a method is evaluated for the request being served. Projects already declaring a method this way will find it enforced from now on, which may close access that was silently open. (HAB-948) -
BREAKING:
DEFAULT_PERMISSION_CLASSESof the admin API now defaults torest_framework.permissions.IsAuthenticatedinstead ofAllowAny— an unconfigured project no longer exposes anonymous CRUD over every registered model. A project that relied on the open default must setADMIN_REST_FRAMEWORK["DEFAULT_PERMISSION_CLASSES"]explicitly. (HAB-900) -
Added the
habarico.W001system check, warning onmanage.py checkand server start when the effectiveDEFAULT_PERMISSION_CLASSESof the admin API containsAllowAnyor is empty; intentional anonymous access is silenced withSILENCED_SYSTEM_CHECKS = ["habarico.W001"]. (HAB-900) -
The
_reverse_fieldand_reverse_filterfilters now permission-check the view they clone. Both take the target path from the client and handed it toclone_view(), which builds a view without runningdispatch()- so no permission ever ran on it. Probing paths and ids turned the filters into a membership oracle over resources the caller cannot read. Three things changed: only URLs inside the habarico namespace resolve at all, only actions whose permissionAdminPermissionactually evaluates may be cloned (the generatedschemaroutes are not among them, and cloning one passed the check without anything having been checked), and every rejection now comes back as the same validation error, so a missing record cannot be told apart from a forbidden one. Found in the HAB-894 security audit. (HAB-905) -
BREAKING: A lazy
ftemplate (habarico.utils.f/LazyFString) is no longer evaluated as a Python f-string.evalgave every template the full language and the builtins, so a template built from anything editable — a translation, a CMS label, a value from the database — was remote code execution. Templates now render throughstr.format_map, and each one is checked when it is constructed: a placeholder may be a name with.attribute/[key]hops, a conversion and a format spec, and no segment may start with an underscore (which is what closes{obj.__class__.__init__.__globals__[...]}). Expressions, conditionals and method calls stop working and raiseValueErrorat import time — compute such a value in aget_<attr>()schema getter instead. Found in the HAB-894 security audit. (HAB-906) -
Updated
djangoto==5.2.17anddjangorestframeworkto==3.17.2, addressing the CVEs found in the HAB-894 security audit (django: CVE-2026-15830 and 11 further CVEs;djangorestframework: CVE-2026-73228, CVE-2026-73229). (HAB-896) (HAB-896) -
User preferences are now size-limited, mirroring the Ruby backend. A single preference value is capped by
HABARICO_PREFERENCES["MAX_VALUE_BYTES"]— a dict resolved by the key, or pattern, a preference was registered under, falling back to its"default"entry (16 KB by default, 32 KB forpreferences.dashboardand*.savedQueries) — and the number of preferences one user may hold byMAX_KEYS_PER_USER(2000). Previously neither was bounded: the pydantic models carry no length constraints,SavedQueryValue.filteris a free-form object, and the pattern keys match an unbounded set of names, so any authenticated user could fill the preference table with arbitrarily large rows, in arbitrary numbers. Both refusals are422. The count is checked only when a new key is created, so a user already past the limit can still overwrite or shrink what they stored, and stored values are never re-checked on read. A key registered by the host app takes the"default"entry unless it passes its ownmax_bytes; aNoneentry in the dict, or aNoneMAX_KEYS_PER_USER, turns that limit off.MAX_VALUE_BYTESis read when a preference is registered, likeDEFAULTS. The defaults sit an order of magnitude above what the frontend writes, but a project that stores unusually large preferences should raise them. Found in the HAB-894 security audit. (HAB-916)
[5.10.0] — 2026-08-03
Fixed
CzechIdCardNumberValidatornow accepts the actual Czech ID document formats (9 digits, 6 digits, or 6 digits with a 2-letter series optionally followed by 2 digits, prepended or appended) instead of only 9 digits (HAB-551).
Added
- Fulltext search highlights in list results (HAB-453).
- Sidepanel view mode and single-click row preview in list views (HAB-594).
exportaction type withExportActionParameters/ExportActionDescriptorand thelist_actionsslot for table utility actions in the list view schema (HAB-529).bottom_actionson all box types — renders action buttons below the box fields, replacing the previous behaviour wheretoolbar_actionswas used for bottom-of-box buttons.list_actionsslot onListBoxfor utility actions (export, print, …) rendered next to the filter bar.list_toolbar_actionsslot onListBox, emitted astoolbar_actionsinside thelistparams — the resource list's own table toolbar (e.g. create), separate from the box-leveltoolbar_actionswhich belongs to the box root (HAB-700).
Changed
- Generalized schema getter dispatch and made box params lazily built from context (HAB-598).
- Unified terminology:
ViewModeType.MODALrenamed toViewModeType.DIALOGand the wire value changed from"modal"to"dialog"; all related docstrings, OpenAPI spec, README, and tests updated (HAB-701). ListBox: the box-leveltoolbar_actionsnow targets the box root (box/card header), consistent with all other box types; the table's own toolbar is set via the newlist_toolbar_actionsinput, which is emitted astoolbar_actionsinside thelistparams.
[5.9.0] — 2026-07-09
Added
- Nested
FieldOptionssupport, with the related API update (HAB-569).
[5.8.2] — 2026-07-06
Fixed
- Corrected the
acceptconfiguration on the upload descriptor (HAB-586).
[5.8.1] — 2026-07-06
Added
- New parameters on the upload descriptor (HAB-586).
[5.8.0] — 2026-07-06
Added
- Chunked upload contrib app with S3 and tus providers (HAB-582).
Changed
- Reworked tab/wizard box navigation parameters (HAB-586).
[5.7.0] — 2026-06-26
Baseline for this changelog; see Git history for earlier releases.