Changelog¶
Unreleased¶
New features¶
Signing service token. RemoteSignerClient now sends
Authorization: Bearer <FIRMADOR_TOKEN> on every call to the signing
service (sign, validate and complete), so it can talk to a firmador_api
deployed with API_TOKEN. The setting is optional: when it is unset or
empty no header is sent and behaviour is unchanged.
Signing service replica affinity. firmador_api keeps each document in
memory between firme and completa, so with several replicas the second
call must reach the instance that served the first. When the signer runs with
IS_DISTRIBUTED=true its firme answer carries that instance’s URL in
hostname; the client now keeps it server side in the Django cache (keyed by
document id, FIRMADOR_AFFINITY_TTL seconds, default 3600) and sends
completa there. Only a bare http(s)://host[:port] is accepted, and it
never goes through the browser. Without it – a single signer, or
FIRMADOR_AFFINITY = False – FIRMADOR_SIGN_COMPLETE is used as before.
Use a cache shared by all workers (database, Redis) if signing can hop between
processes.
Leaflet map widgets. Two widgets sharing one JavaScript engine
(gentelella/js/base/maplib.js):
MapPointInputasks the user for a single GPS point – click, marker drag, “use my location”, address search orbased_fieldsgeocoding from other fields. It stores the plain string"latitude,longitude", so no GeoDjango, GDAL or PostGIS is needed and it works on SQLite.djgentelella.fields.mapsadds the matchingGTPointFieldmodel field (with a properdeconstruct()) andGTPointFormField.DJMapdraws many points fetched from an API, the sibling ofDJGraph: marker popups, per-layer clustering, a heatmap and a layer switcher. SubclassBaseMapViewin<app>/gtmaps.pyand includegentelella/widgets/djmap.html. Filters use the same#selector/{funcName}data-*syntax as the chart widget.
Leaflet is always loaded. Marker clustering and the heatmap come with the new
use_maps define in DEFAULT_JS_IMPORTS; without it maps still work, with
clustered layers falling back to plain layer groups.
Four new bundles are produced by pylp:
djgentelella.maps.vendors.min.{js,css} and
djgentelella.maps.plugins.min.{js,css}. They are emitted after the
readonly bundle on purpose – storymapjs embeds Leaflet 0.7.7 and assigns it to
window.L, so the reverse order silently downgrades every map.
See docs/source/widgets/maps.rst.
Two generic javascript components. Both are classes concatenated into
base.js by createbasejs, with a stylesheet of their own linked from
gentelella/statics/stylesheets.html.
PositionsGrid(gentelella/js/base/positionsgrid.js) draws a matrix of rows and cells where each cell holds zero or more opaque items. Rows are irregular on purpose: a row of two cells is half the width of a row of four, cells stay left aligned and nothing is padded out to a rectangle, because the shape is the host’s data and not a grid the widget imposes. The widget keeps no state of its own and serialises nothing – every mutation goes out through a handler that resolves with the new state and the grid repaints with that, so a rejected handler leaves the screen showing exactly what the server last confirmed and firespg:error. Moving an item is two taps (pick, then destination), not a drag, so it works on a phone and from the keyboard; the desktop drag is the same code path.BreadcrumbNav(gentelella/js/base/breadcrumbnav.js) fills the{% block breadcrumbs %}ofbase.htmlandplain.html, which had been empty since the theme was ported. A view that putsbreadcrumbsin its context now gets the path server-rendered fromgentelella/blocks/breadcrumb.html; pages that do not are unaffected, the include renders nothing.
The demo warehouse at /positionsgrid_view wires all seven handlers to real
endpoints and refuses to delete a row that still holds boxes, which is the only
way to see the rejection path on screen.
See docs/source/appwidgets/positionsgrid.rst.
The history knows what an entry was about. A LogEntry only points at
the object that changed, so an application that wants “everything that happened
in this laboratory” had nowhere to hang that context and ended up matching on
the translated change_message. The new HistoryRelation model (migration
0019) attaches any number of objects to one log entry through a generic
foreign key, plus a data JSONField per relation:
add_log()takesrelated_objects=(model instances, or(instance, data)tuples – a bare pk raisesValueError, because a pk alone does not say which model it belongs to) andextra=, and it now returns theLogEntryit created. A customchange_messageon a DELETE is no longer overwritten.Entries logged with no authenticated user go to the username in the new
GT_HISTORY_ANONYMOUS_USERNAMEsetting, so a project can point them at its own sentinel account instead of losing the entry.HistoryViewSetgained ascope_queryset()hook, therelated_contenttype/related_idfilters and an?extra={"k": v}filter that chains one to many keys against the same relation row (and works on SQLite). ItsrecordsTotalis now the scoped universe: the global count leaked the platform’s volume to every tenant. A project withoutGT_HISTORY_ALLOWED_MODELSno longer hits aTypeError.BaseViewSetWithLogswas repaired throughout:models_logreplaces the attribute that never existed,perform_destroypassesdelete(user=)forDeletedWithTrashmodels,perform_updateresolves fields bysource, andget_log_related_objects()/get_log_extra()let a subclass add context without overriding the whole method. Withlog_request_metadatathe browser, IP, method and path are captured on their own.
The trash records the context of a deletion, and can be scoped.
TrashRelation (migration 0020) is to Trash what HistoryRelation
is to LogEntry: DeletedWithTrash.delete(..., related_objects=[...]) –
and the queryset-level delete(user=, related_objects=) – record which
objects the deletion belonged to. The first deletion wins: re-deleting
something already in the trash keeps the standing context, and a restore
cascades it away so the next deletion records afresh.
TrashViewSet mirrors the history viewset: the same
related_contenttype / related_id filters, the same scope_queryset()
hook, and a scoped recordsTotal. Because restore and destroy
resolve through get_object(), the hook narrows those actions too, which is
what a multi-tenant project needs to keep one tenant out of another’s trash.
Restore now respects scoped subclasses, an entry whose object is gone answers
410 instead of failing, its permission check is aligned with
change_trash, and entries can be filtered by deleted_by.
Both models are registered in the admin. djgentelella.utils
.contenttypes_from_labels is the single place where app_label.model
strings become ContentTypes, shared by history and trash.
See docs/source/history.rst and docs/source/trash.rst.
Backwards incompatible¶
The help palette showed its own template placeholders. The panel builds
each entry by cloning a hidden prototype block and swapping $title and
$text into it. That block was hidden with class="hidden" – a Bootstrap
3 utility, dropped in Bootstrap 4, defined neither by Bootstrap 5 nor anywhere
in this project – so it was not hidden at all, and the raw $title,
$byline and $text sat at the bottom of every help panel. It is
d-none now.
The palette had no tests, because it only renders once a MenuItem points at
it and the demo’s menu comes from a management command the suite never runs.
Five browser tests now create that menu item and cover the panel opening, the
prototype staying invisible while keeping its placeholders, a stored entry
listed with its text, and the question mark landing on the label of the field
it documents.
Two fixes for forms that live in a modal.
TinyMCE arrived empty at the server. The editor writes its content back into
the <textarea> only when the form is really submitted, through a hook it
installs on the form element. A modal is not submitted – its javascript reads
the fields and posts JSON – so the textarea still held whatever it had when
the editor booted, usually nothing, and the field came back “this field cannot
be blank” over a box with visible text in it. obtainFormAsJSON() now
flushes every editor in the form before reading it.
And it arrived empty at the editor. The other direction named two widgets by
hand, EditorTinymce and TextareaWysiwyg, so editing an object whose
field used any other editor widget – VoiceEditorTinymce, or one a project
subclasses – wrote the stored value into the hidden textarea and left the
editor blank over data that was there; saving then wiped it. It asks TinyMCE
whether it manages the element instead, which covers every variant.
Both faults reached async_notification as well – every message field there
is an EmailEditorTinymce inside a create/update modal – so four browser
tests now cover that screen too: the editor’s content reaching the server, a
stored message reaching the editor, the preview button, and the widget keeping
data-widget="EditorTinymce" so the shared initialiser runs. Unlike the
compose test, which was the module’s only browser coverage, they need no
MailHog.
``ChunkedUploadBaseView.login_required``. The upload views refused every
anonymous request with a 403 baked into check_permissions(), so a public
form with a file on it could not work and there was nothing to override short
of rewriting the method. It is a class attribute now. The default is still
True – an open upload endpoint is somewhere for anyone to park files – and a
project that wants otherwise subclasses:
class PublicUpload(ChunkedUploadView):
login_required = False
Note that an anonymous upload cannot be scoped to its owner, so its 32
character upload_id is the only thing protecting a partial upload;
get_max_bytes() still caps the size.
The upload widgets got their missing half. Their file input has always been
transparent – that is what makes the whole area a click target and a drop
target – but nothing was ever drawn underneath, so an empty widget rendered as
a blank white box with no label, no button and no hint that it could be clicked
at all. gentelella/css/fileupload.css draws that surface, and it is a
backdrop behind the input rather than a replacement for it, so no javascript
changed. The same file gives the widgets a real progress bar (the only sign an
upload was running used to be a percentage inside a 3 rem chip), makes the
download control neutral instead of bg-danger – a red button, in a palette
where red is how deletion is spelled, for the one control that only reads –
and turns the delete row into a labelled checkbox that goes red only once it is
armed. PDFViewerWidget gets the same treatment. Colours and the accent are
--gt-upload-* custom properties.
``FileChunkedUpload`` on a model ``FileField`` now has tests, and the demo
can actually delete. The widget already resolved the upload token into the
uploaded file (value_from_datadict) and rendered a file that came from the
database (format_value), so a FileField needs nothing but
widget=FileChunkedUpload – but none of that round trip was covered. Four
browser tests now walk it: the upload is saved into the model field byte for
byte, reopening the record shows the stored file with its download link, saving
without touching it keeps the file, and ticking delete this file clears it.
That last one exposed a demo bug: ChunkedUploadItem.fileexample was
declared without blank=True, so the delete checkbox the widget ships could
only ever produce “This field cannot be blank” on submit. Any model that
offers that checkbox needs a field that allows blank; the demo’s does now
(migration 0028).
The chunked upload is ours now: blueimp jQuery-File-Upload is gone. The
upload group was four vendored files; it is one. jquery.fileupload.min.js
is replaced by gentelella/js/base/chunkedupload.js, which speaks the
protocol chunked_upload/views.py already defined – slice, POST with a
Content-Range, thread the upload_id, then hand over the checksum – in
about 130 lines of fetch. With it go jquery.ui.widget.min.js, which was
only there because blueimp is built on the jQuery UI widget factory, and
jquery.iframe-transport.min.js, a fallback for browsers with no XHR file
upload, meaning IE9. spark-md5 stays: it hashes a file incrementally, which
SubtleCrypto cannot do, and the alternative is holding the whole upload in
memory to checksum it.
FileChunkedUpload and PDFViewerWidget had a copy each of the same
uploader and the same md5 helper; both now call the shared one.
This fixes a race that could reject a good upload. The checksum was
computed in the background while the upload ran, so a small file on a fast
connection finished first and the completion request went out with an empty
md5 – the server answers “Both ‘upload_id’ and ‘md5’ are required”, and
the file is lost after transferring fine. The hash is now awaited before the
first slice is sent.
Uploads had no browser test at all, which for a widget that is entirely javascript meant none. Four now push a real 240 kB file through a real browser and compare what landed on the server byte for byte, check that a small file’s completion call is accepted, that progress is reported, and that a refused upload rejects with a message to show rather than failing silently.
FullCalendar 5.11.3 -> 6.1.20, and it no longer has a stylesheet.
FullCalendar 6 injects its own CSS from javascript, so
vendors/fullcalendar/main.min.css does not exist any more: it is gone from
the readonly CSS bundle and from stylesheets.html, and a project that
linked it by hand has to drop the link. The script is one global build with the
standard plugins already in it, index.global.min.js, instead of
main.min.js. locales-all.js is no longer downloaded either – it moved
to another package in 6, and no template had ever loaded it.
The widget API this library uses did not change: new FullCalendar.Calendar,
eventDidMount, eventClick, dateClick and the EventApi setters
all behave the same, and so do the three CSS overrides in calendar.html
that target fc-daygrid-day-frame and friends – which is exactly what a
major version is free to rename, so the calendar now has six browser tests
that pin the month grid, the injected styles, those overrides, the event
tooltip and the detail modal. It had none.
TinyMCE 5.6.1 -> 8.8.2, under the GPL licence key. From TinyMCE 7 the
editor is GPL-2.0-or-later, and from 8 a self-hosted build loads read-only
unless a licence key is set. The shared config now sends
license_key: 'gpl', which is the open source option and matches what this
package ships under. A project that has bought a commercial key sets its own
by overriding the config.
Three things moved in the package itself:
jquery.tinymce.min.jsis not part of TinyMCE any more – the jQuery integration became a separate project – so$(el).tinymce(config)is nowtinymce.init({target: el, ...}), wrapped in a newgentelella_tinymce_init(). Remove the second script tag if you load TinyMCE by hand.models/dom/model.min.jsis new and mandatory: without it the editor does not start at all.The mobile theme and its
*.mobile.min.cssskins are gone.
The plugin list went from 46 to 29. paste, print, hr,
colorpicker, contextmenu, textcolor, noneditable and
tabfocus were absorbed into the core; bbcode, legacyoutput,
spellchecker and textpattern were dropped; fullpage,
imagetools, template and toc became premium. The toolbar was
trimmed to controls that exist: it had been asking for checklist,
casechange, permanentpen, formatpainter, pageembed,
template, a11ycheck, showcomments and addcomment, all premium
and all drawing nothing. fontselect, fontsizeselect and
formatselect are fontfamily, fontsize and blocks.
directionality is now actually loaded, so the ltr/rtl buttons the
config had always asked for finally appear. paste_preprocess takes the
editor as its first argument instead of the plugin.
A project that passed its own plugins or toolbar has to check them
against the 29 that remain.
``VoiceEditorTinymce`` needed one change, and it is the kind that fails
silently: a toggle button’s setDisabled became setEnabled in TinyMCE 6,
with the sense reversed. Three browser tests now drive that button with a fake
microphone, so the whole path – the icon and button registration,
setActive, setEnabled, setProgressState and the notification
manager – is exercised rather than assumed.
jQuery-Knob is gone; the dial of ``NumberKnobInput`` is an SVG of our own.
The old plugin (2015, unmaintained) painted a <canvas> at a fixed pixel
size – soft on any display past 1x – and rebuilt its own keyboard handling
over an input it had emptied of meaning. gentelella/js/base/knob.js plus
css/knob.css draw a stroked circle instead, and leave the
<input type="number"> in the middle of it as the value, the readout and the
focusable control: arrow keys, typing, min/max/step validation and
what a screen reader announces are the browser’s own, and the svg is
aria-hidden, adding only what an input cannot do – dragging round the dial
and the wheel.
It reads the same data-* options the old plugin did (data-min,
data-max, data-step, data-width, data-fgcolor,
data-bgcolor, data-thickness), so a form that configured a knob needs
no change; data-displayprevious, data-cursor and the other
canvas-drawing options are ignored, since there is no canvas to draw. Size and
colour are also --gt-knob-size and --gt-knob-color, settable in CSS.
Two long-standing bugs went with it. NumberKnobInput declared
input_type = 'number' but its template never wrote the type attribute,
so the browser rendered a text field – invisible while a canvas covered
it, and the reason the keyboard did nothing. And data-min/data-max/
data-step are now mirrored onto the native min/max/step, so the
browser’s own arrows and validation agree with the dial instead of stepping by
1 through a field declared data-step="0.1".
iCheck and switchery are gone; checkboxes, radios and switches are CSS.
Both libraries worked the same way: hide the real <input> and paint their
own DOM beside it. That is why every caller had to go through an imperative
API. gentelella/css/checks.css draws the native input instead
(appearance: none plus a background image), so the control the user clicks
and the field the form submits are the same element again.
What that deletes, rather than ports:
.iCheck('check' | 'uncheck' | 'update')– 13 calls acrosspermissionmanagement.js,form.common.js,obj_api_management.jsandcustom.js. Settinginput.checkednow repaints on its own.ifChecked/ifUnchecked/ifToggled– 8 handlers, replaced by the nativechangeevent.The
data-switcherybookkeeping inclear_action_form(): a native form reset repaints the switches by itself.The hand-written
<div class="icheckbox_flat-green">…<ins class="iCheck-helper">inchunkedupload.html,file.html,pdfviewer.html,crud_list.htmland the javascript template incustom.widgets.js.17 vendored files (both libraries, five skin stylesheets and ten sprite PNGs), their
loaddevstaticentries, their fourpylpfileentries and the.switcheryoverrides in the four theme stylesheets. 36 KB of vendor code becomes 6 KB of ours.
What changes for a project: the widgets stamp gt-check (checkbox, radio,
multiple checkbox) or gt-switch (YesNoInput) instead of flat, and
markup written by hand against icheckbox_flat-green has to become a plain
<input class="gt-check">. data-checkboxclass and data-radioclass,
which chose an iCheck skin, no longer do anything; colour and size are
--gt-check-color and --gt-check-size, settable at runtime.
Three things work now that did not: the indeterminate state (the sprite had no
frame for it, so a partially selected “select all” box drew as empty), keyboard
focus (both libraries hid the input, so the focus ring went with it), and
prefers-reduced-motion.
moment no longer ships all 137 languages to every page. The bundle carried
moment-with-locales.min.js, 375 KB, so that one language could be used. It
now carries moment’s core build (59 KB) and the page links the locale file for
the active language separately (~4 KB), through the new get_moment_locale
tag in gtsettings. loaddevstatic downloads the locales named by
settings.LANGUAGES, so a project that narrowed that setting downloads only
its own languages, and one that never set it keeps Django’s full list and loses
nothing. English needs no file: it is built into the core build, and a language
moment has no translation for degrades to English rather than 404ing.
daterangepicker keeps moment – it mutates moment objects in about twenty places, so dayjs, which is immutable, is not a drop-in replacement for it.
DataTables 1.12.1 -> 2.3.7, and 14 extensions dropped from the build. The
bundle carried AutoFill, DateTime, FixedColumns, FixedHeader, KeyTable,
RowGroup, RowReorder, Scroller, SearchBuilder, SearchPanes, Select,
StateRestore, jszip and the colvis/html5/print buttons without a single
reference anywhere in the project – row selection is hand rolled with
.gtcheckable checkboxes, not the Select extension. What is left is
DataTables itself plus Buttons, ColReorder and Responsive: the vendored
directory goes from 668 KB to 194 KB.
What changes for a project that styles or scripts its own tables:
Every class DataTables generates was renamed,
dataTables_*->dt-*:dataTables_wrapperisdt-container,dataTables_filterisdt-search,dataTables_paginateisdt-paging,dataTables_lengthisdt-length,dataTables_infoisdt-info,dataTables_processingisdt-processing,dataTables_emptyisdt-empty. The four theme stylesheets were updated, and thepaging_full_numbers,DTTT_button(TableTools, gone since 2014),table.displayandexample_alt_paginationrules – all DataTables 1.9 markup that nothing in this project has emitted for years – were deleted rather than renamed.The ``dom`` string is now a ``layout`` object.
document.table_default_domisdocument.table_default_layout; a page that set its owndom(the notification list did) has to be ported.``dt.context[0].nTable`` is gone; use
dt.table().node().The i18n files come from
plug-ins/2.3.7/i18n/and their keys changed.DATATABLES_SUPPORT_LANGUAGESstill points at them the same way.
The server side is untouched: formatDataTableParams already translated the
request into DRF’s offset/limit/ordering, and
draw/recordsTotal/recordsFiltered mean the same thing in
DataTables 2. Seven browser tests now cover the wrapper, which had none.
Chart.js 2.9.3 -> 4.5.1. The whole chart configuration is built by
djgentelella.chartjs and handed to new Chart() untouched, so the option
vocabulary in the serializers is now Chart.js 4’s:
The get_<option>() hooks kept their names – get_title, get_legend
and get_tooltips name a chart concept, not a configuration path – and the
last five rows of that table are translated for a release: a get_scales
still returning xAxes/yAxes, an elements.rectangle block, a
steppedLine dataset key and a get_type returning horizontalBar all
keep working. What does not survive is a tooltip callback written for
Chart.js 2: v3 replaced the (item, data) arguments with a single context
object, so every function registered in document.chartcallbacks has to be
rewritten. The two shipped with the library already are.
options.plugins accepts anything, so third party plugins configured through
get_plugins() are passed through untouched.
Two smaller consequences: vendors/chartjs/Chart.min.css is gone (Chart.js 3
dropped the stylesheet, everything is drawn on the canvas) and the script is
vendors/chartjs/chart.umd.min.js. A project that linked either path by hand
has to update it. gentelella/widgets/chart.html, an orphan template no
include ever pointed at, was deleted; the live one is
gentelella/widgets/chartjs.html.
Five javascript libraries are gone from the bundles. None of them was doing anything, and all five were downloaded and shipped on every page:
Parsley (42 KB) – no
data-parsley-*attribute and no.parsley()call has ever existed in the project.patternfly-bootstrap-treeview (27 KB) – nothing calls
.treeview(); theTreeSelect*widgets are drawn by select2. It was also the only dependency fetched from a rawgithub.comURL.bootstrap-progressbar – the plugin only reacts to
data-transitiongoaland no template sets it, so$('.progress .progress-bar').progressbar()animated nothing. The call incustom.jswent with it.jQuery UI’s stylesheet – downloaded, never linked. The one piece of jQuery UI still in the build is blueimp’s private
jquery.ui.widgetshim, which comes with the file-upload plugin.A second copy of interact.js – it was downloaded into both
vendors/interact/andvendors/pdfjs/, and both were loaded.
A project that referenced any of them directly has to vendor it itself.
Javascript bundles are now joined with ``;n``. A minified vendor file that
ends in an expression without a trailing semicolon, followed by one that starts
with (, used to concatenate into a single call expression: the bundle then
died at parse time and every library after that point disappeared silently.
Nothing to change downstream, but a custom pylpfile.py copied from this one
should pick up the sep=JS_SEP argument.
Library upgrades. jQuery 3.6.1 -> 3.7.1, inputmask 3.3.11 -> 5.0.10 (only
the jQuery build is shipped now; it already contains the whole library),
bootstrap-daterangepicker 3.0.5 -> 3.1.0, bootstrap-maxlength 1.10.0 -> 2.0.0
and ion-rangeslider 2.3.1 -> 2.3.2. The maxlength counter’s
warningClass moved from the Bootstrap 3 label label-success to
badge text-bg-success.
Two dead asset links removed. vendors/tagify/jQuery.tagify.min.js and
vendors/pdfjs/pdf_viewer.min.css were requested by
gentelella/statics/*.html but not downloaded by loaddevstatic: a 404 on
every page in development and a hard failure under
ManifestStaticFilesStorage. The uncompressed branch also loaded
fileupload, tagify, TinyMCE and SweetAlert2 twice, and htmx not at all – htmx
was only in the compressed bundle, and its vendor folder was misspelled
vendors/htmlx/. It is vendors/htmx/ now.
Country flags are no longer CSS classes. <i class="fi fi-cr"></i> and
the use_flags define are gone, along with
djgentelella.flags.vendors.min.css. Replace them with the gtflags
template tags:
{% load gtflags %}
{% flag 'cr' %} <!-- was <i class="fi fi-cr"></i> -->
{% flag 'cr' square=True %} <!-- was <i class="fi fi-cr fis"></i> -->
That stylesheet was 6.35 MB. It carried all 532 flag SVGs base64-inlined into
background-image, a 232x inflation of the 27 KB it started as, and a page
showing three flags downloaded every country in both aspect ratios before it
could render. The 532 SVGs were shipped a second time under
static/vendors/flags/, and loaddevstatic re-downloaded all of them from
a CDN on every run – a network hiccup during a release quietly produced broken
flags.
Removing it also frees the .fi class, which flag-icons 6.6.6 and Friconix
both claim: with use_flags on, every Friconix <i class="fi fi-..."> was
silently picking up flag-icons’ background-size, display and width.
Projects that never used flags need to change nothing.
New features¶
Country flags from a committed sprite, served on demand. All 266 flags now
ship as one gzipped SVG sprite,
static/gentelella/flags/flags.4x3.svg.gz (~650 KB), built by the new
manage.py buildflagsprite command and committed to the repository – nothing
is downloaded at build or install time.
{% flag 'cr' %}(djgentelella.templatetags.gtflags) renders an<svg><use>into the sprite: one request serves every flag on the page, cached across pages.square=True,css_classandtitleare supported, and an unknown code renders nothing./flags/<code>.svgreturns a single flag, with optionalsize,shape(circleorrounded) andtitleapplied at request time.djgentelella.flags.flag_url()builds the URL from Python./flags/sprite.svgserves the sprite, handed over still gzipped.Flags in a select2 autocomplete need no new JavaScript: a
BaseSelectImg2Viewwhoseget_url()returnsflag_url(obj.code)makesAutocompleteSelectImagedraw a flag per option. The demo shows it at/imageselect/create/, where the same widget sits beside one fed by uploadedFileFieldimages – the only difference between them is whatget_url()returns – and on the person form, where it also has to survive being cloned into a formset row and a modal.
cefta was missing from the download list, so .fi-cefta had been defined
in the CSS and 404ing since 6.6.6 landed; the sprite has it.
The wheel is 2.4 MB smaller to download (11.8 MB -> 9.5 MB) and about 11.9 MB smaller once installed, where the base64 and the SVGs were no longer compressed.
See docs/source/icons.rst.
Fixes¶
loaddevstatic dropped the last chunk of its download queue whenever the
number of files was an exact multiple of the thread count, so a library could
silently never appear under vendors/.
MapPointInput leaked event handlers. The based_fields listeners are
bound on other fields, and destroy() only unbound the widget’s own input,
so a re-rendered widget – a formset row, a reopened modal – left a live
handler on a sibling field closing over a map that no longer existed. A
malformed data-based-fields also threw out of JSON.parse and killed the
rest of the widget initialisation, leaving an input with no map; it now falls
back to no geocoding and reports the problem on the console.
TrashViewSet.restore printed the exception to stdout instead of logging it,
so the reason a restore failed never reached the application log.
Maps rendered as empty squares on a default Django project. Django sets
SECURE_REFERRER_POLICY = 'same-origin', so the browser sends no Referer
on the cross-origin requests every tile is, and OpenStreetMap’s tile policy
answers 403 without one. The demo now sets
SECURE_REFERRER_POLICY = 'strict-origin-when-cross-origin' and
docs/source/widgets/maps.rst says why any project using these widgets has
to do the same. The demo also listed SecurityMiddleware twice.
Material Design Icons, opt-in. 7448 icons as a webfont, shipped inside the package – nothing is fetched from a CDN at runtime – and loaded only where a template asks for it:
{% load gtsettings %}
{% block pre_head %}{% define_true "use_mdi" %}{% endblock %}
<i class="mdi mdi-home"></i>
Only the woff2 is downloaded. Upstream’s @font-face also lists eot, woff and
ttf – the same glyphs in three encodings no supported browser would pick – so
loaddevstatic rewrites the rule to the woff2 alone instead of shipping
2.3 MB of fonts. It is deliberately not part of any pylp bundle: urlreplace
would base64 the font into djgentelella.vendors.min.css, and the point of a
webfont is that the browser fetches one 403 KB file and caches it.
PDF viewer widget. PDFViewerWidget displays a PDF from a FileField
with page navigation and zoom, rendering client-side through pdf.js;
PDFFileField validates the extension, the content type and the magic bytes,
and uploads go through a PDF-specific chunked endpoint
(upload_pdf_view / upload_pdf_done) rather than the generic one. Demo at
/pdfviewer/.
gentelella/css/pdfviewer_widget.css is linked from the base template rather
than left for each project to remember, on the same reasoning as maps.css:
without it the canvas has no size and the controls no layout.
A reference page per icon set, under an Icons menu in the demo sidebar:
/icons/fontawesome (786), /icons/friconix (1559), /icons/mdi (7448)
and /icons/flags (266). Each is searchable, and each reads its set from what
the browser already loaded – friconix’s paths global, the CSS rules Font
Awesome and MDI declare, the <symbol> elements of the flags sprite – so no
list can drift from the version loaddevstatic downloaded. The flags page
also previews the square, circle and rounded shapes, and the
friconix page lets you try a mask against every icon at once.
Dependencies¶
Eleven frontend libraries moved up, all within a compatible range and verified against the browser suite (57 Selenium tests):
select24.1.0-rc.0 -> 4.1.0 – the bundle had been shipping a release candidate.moment.js2.13.0 -> 2.30.1. The pin was from 2016 and sat below every security fix in the 2.29.x line.bootstrap5.2.0 -> 5.3.8 and@popperjs/core2.11.5 -> 2.11.8.parsley.js2.3.13 -> 2.9.2,@yaireo/tagify4.33.2 -> 4.38.0,htmx.org2.0.4 -> 2.0.10,squirrelly9.0.0 -> 9.1.1,spark-md53.0.0 -> 3.0.2,iCheck1.0.2 -> 1.0.3,interact.js1.10.27 -> 1.10.28.
interact.js was also being downloaded twice, once from an unpinned
cdn.jsdelivr.net/npm/interactjs/ that resolved to whatever was current that
day. Both copies are now pinned to 1.10.28.
pdf.js 4.6.82 -> 6.2.108. globalThis.pdfjsLib is still exposed by the
.mjs build and getViewport/render are unchanged, but 6.x dropped
``getDocument``’s bare-string shorthand: a relative string, an absolute string
and even a URL object are all rejected with “expected either data,
range, or url parameter”, and the rejection is asynchronous, so the
canvas simply stays blank at its default 300x150. Both call sites now pass
{url: ...}.
pdf_viewer.min.css is gone from the bundle: 6.x no longer publishes it, and
it styled only the text, annotation and XFA layers, none of which anything here
renders. Its 15 images/ SVGs went with it – they existed solely to feed its
url()s.
Two more moved a major version each, both verified in a browser rather than by
version number alone: autosize 3.0.15 -> 6.0.1 (the global still takes a
textarea and still grows it) and sweetalert2 10.10.0 -> 11.26.25
(Swal.fire, Swal.mixin, stopTimer/resumeTimer and the
didOpen hook all survive; none of the options v11 dropped were in use).
patternfly-bootstrap-treeview was downloaded from the master branch and
is now pinned to v2.1.10, which is byte-identical to what master serves
today – a pin that swaps nothing.
Checksums for the sources that cannot be pinned. Knightlab’s StoryMap and
Timeline are fetched from latest, and friconix from a URL with no version at
all, so two builds of the same commit could differ with nothing to show for it.
loaddevstatic now records a SHA-256 for each and reports, in a summary block
at the end of the run, any that changed since they were last vetted.
Knightlab’s numbered releases are deliberately not used: latest is the
current webpack build of StoryMapJS (260 KB, KLStoryMap namespace) while its
newest tag, 0.7.1, is unminified 2019 code on the old VCO architecture at
twice the size. Pinning to it would be a four-year downgrade, not a pin –
and it embeds a different Leaflet, which maplib.js asserts against at
runtime.
Removed from loaddevstatic:
summernote 0.8.18 – downloaded and packaged, but in no bundle and no template. The only caller was
uploadFile()ingentelella/js/base/wysiwyg.js, which nothing called either and which would have thrown$(...).summernote is not a functionif it had. Both are gone; the wysiwyg widgets use TinyMCE’s own upload endpoints.Bootstrap 3 glyphicons (216 KB, five font files from the retired MaxCDN) – no bundled stylesheet has ever referenced one.
vendors/fonts/now holds only the Font Awesome faces thatfont-awesome.min.cssactually reaches.A
bootstrap-datetimepickersourcemap that has always 404ed, printing aFAILEDline on every run.
pdf.js parsed every document on the main thread. The worker was loaded as a
second <script type="module">, which downloads a megabyte into the page and
leaves GlobalWorkerOptions.workerSrc empty – so pdf.js silently fell back
to its fake worker and blocked the UI while parsing. It is now handed to pdf.js
as a URL, and the parsing really does happen off-thread.
Fixes¶
Friconix icons never appeared on anything built after page load. Friconix
hooks document.onreadystatechange, scans once and has no
MutationObserver, so an <i class="fi-..."> inside a formset row, a
reopened modal or a DataTables redraw stayed an empty element. djgentelella now
rescans from gt_find_initialize() – the hook every widget already
re-initialises through – and exposes gt_friconix_refresh() for markup
injected by hand. Removing the flag-icons stylesheet helps here too: it defined
.fi, the same class friconix marks its icons with.
The demo gained a searchable grid of all 1559 icons at /friconix, and
docs/source/icons.rst now documents the six-letter mask grammar instead of
pointing at an external site.
Tooling¶
make test-selenium now runs inside its own X server (xvfb-run -a), so
the browser the tests drive cannot steal focus from the developer’s session.
SELENIUM_HEADLESS=0 draws a real Chrome inside it – useful for the
Leaflet, canvas and TinyMCE widgets – and make test-selenium-run keeps the
old behaviour of running on the caller’s display.
make coverage and make coverage-all measure the package, the second one
combining the unit and the browser suites into a single report. Configuration
lives in pyproject.toml; coverage is not a declared dependency, install
it yourself. The unused [tool.black] section was dropped – the formatter
this project actually uses is ruff format.
0.6.0¶
Breaking changes¶
Django 5.2 (LTS) is now the minimum, along with Django REST Framework 3.15.2. Python 3.11 remains the floor.
``InlineAjaxCRUD`` was removed, together with the inlines attribute of
CRUDView, the crud_inline_url template tag and the
templates/cruds/ajax/ template set. It rendered the children of an object as
server side HTML fragments swapped in with djangoajax, a pattern nothing else in
the project used. Replace it with
BaseInlineObjectManagement, which manages
the same relation through the regular ObjectCRUD javascript over a queryset
scoped to one parent:
class NoteManagement(BaseInlineObjectManagement):
queryset = Note.objects.all()
parent_model = Project
parent_field = 'project'
router.register(r'project/(?P<parent_pk>[^/.]+)/note',
NoteManagement, 'api-project-note')
See docs/source/object_management.rst for the full example.
django-markitup is gone from the blog. Entry.resume and
Entry.content are plain TextFields edited with EditorTinymce.
Remove 'markitup' from INSTALLED_APPS and drop the MARKITUP_FILTER,
MARKITUP_SET and markitup_preview URL entries. Existing databases are
migrated by blog.0002, which moves the HTML held in the _resume_rendered
and _content_rendered columns into resume/content and then drops
those columns; without it every insert fails on them (they are NOT NULL) and the
markdown source would be served verbatim. The migration cannot be reversed, and
its raw DROP COLUMN needs SQLite 3.35 or newer — back up the blog tables
before upgrading.
Warning
Entry bodies are now stored and served as HTML written in TinyMCE, and
gentelella does not sanitize them. Anyone holding blog.add_entry or
blog.change_entry can therefore place arbitrary HTML and JavaScript on
the public entry list and detail pages, which carry no permission check.
Grant those permissions to trusted authors only, or wrap the fields in your
own sanitizer if the blog is open to untrusted writers.
The tree select fields are form fields, not widgets.
GentelellaTreeNodeChoiceField and GentelellaTreeNodeMultipleChoiceField
now derive from ModelChoiceField/ModelMultipleChoiceField instead of
forms.Select, so they are declared as the field, not passed as widget=:
# before (never actually worked)
node = forms.ModelChoiceField(queryset=..., widget=GentelellaTreeNodeChoiceField(...))
# now
node = GentelellaTreeNodeChoiceField(queryset=...)
In practice nothing breaks: the previous code called
forms.Select.__init__(queryset) and raised AttributeError on
construction, and it read MPTT internals that have not been a dependency since
2022.
The transcription endpoint answers 403 json instead of redirecting.
voice_transcribe enforces authentication itself and must not be wrapped in
login_required: its caller is the widget’s fetch, which would follow the
302 and fail parsing the login page as json.
Dependencies dropped: djangoajax, django-markitup and markdown
are no longer installed, and the dead static/django_ajax/ files were
removed. The firmador extra now uses lower bounds instead of == pins,
which made it unresolvable next to any project needing a newer requests,
channels or django-cors-headers.
New features¶
Voice dictation widgets (VoiceDictation, VoiceEditorTinymce) with
progressive Web Audio + VAD capture, and a transcription endpoint
(voice_transcribe) with two interchangeable backends selected
by GENTELELLA_ASR_BACKEND:
pip install "djgentelella[asr]" # local, in-process Parakeet-v3
pip install "djgentelella[asr-remote]" # forward to an external ASR API
Neither extra is needed to install or import djgentelella; without the matching
one the endpoint answers 501 naming it. See
docs/source/widgets/voice.rst.
``BaseInlineObjectManagement`` for REST-driven CRUD over the children of a parent object, scoped by the queryset so a child of another parent answers 404.
``celery`` extra for the async_notification queue backend. Without it
notifications are dispatched in-process by SyncBackend, with nothing to
configure.
The project is now classified Production/Stable rather than Beta.
Fixes¶
The tree select fields are rebuilt on
django-tree-queries(see the breaking change above). Options are now indented at any depth: the themes only defined.l2and.l3, so levels 0, 1 and 4+ rendered flush left.TreeSelectMultiplewas never registered inwidgets.jsand did not indent at all.disableNaccepts any depth and honours its value, sodisable1=Falseno longer disables level 1. Replacingfield.querysetafter construction — the usual way to scope a field inside a form’s__init__— keeps the tree depths; it used to silently flatten the whole tree.BaseObjectManagement.listcountedrecordsTotalfrom the class levelquerysetinstead ofget_queryset(), so any subclass narrowing the queryset reported the unfiltered total.decore_form_instancecarried a branch comparingtype(field)against a string, dead since the MPTT removal in 2022; removed.The ASR backend selection treats an empty setting as unset, since both it and the remote URL are usually wired to
os.getenv.MANIFEST.ingrafts the whole package, so a module cannot be released without its templates.async_notification’s 23 templates and 3 static files were absent from the build;make check-distnow fails the release when any tree is missing.make releaseno longer passestwine upload -s: PyPI stopped accepting GPG signatures in 2023.make sdistrunsmakemigrations --checkinstead ofmakemigrations, so a release fails on model drift rather than shipping a migration nobody reviewed.