Date pickers: a picker per calendar view, the agenda's end day, and the calendar beside every date (#933) #1017
No reviewers
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
performance
security
tier
1
tier
2
tier
3
tier/4
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Postulo/postulo!1017
Loading…
Reference in a new issue
No description provided.
Delete branch "933-date-pickers"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #933
One set of date pickers for the calendar and every date in the app, built on the
<c-date-picker>of #928 rather than beside it, as the approved plan says. Six commits, one per part.What changed
1. The component grows three modes (
cotton/date_picker.html,readyDatePickers()inapp.js,assets/css/basecoat.css)mode="week": the row under the pointer or the focus is lit, the days either side of the month are drawn so a week is whole, and any day pressed gives the week's first day.mode="month": a 3x4 grid of months, the arrows beside the year stepping a year; arrows move by a month and a row, Page Up/Down by a year; the choice isYYYY-MM.mode="range": a first press and a last (swapped if earlier), the days between lit as the pointer moves, a status line saying both; the event carriesuntil.min/maxare the caller's (the birth picker's "not after today" is now its page'smax); empty is no limit.input/until_inputthe panel fills those boxes itself and fireschange(readyDateFields()); withsubmitit sends their form.styleand the CSP refuses that (#709's trap).2. The server's first weekday on
<html>:data-first-weekday(Django's numbering, which isDate.getDay()'s) from theuicontext processor, read by every picker;data-week-numberswhere the language numbers weeks (German, the Nordic languages), which adds an ISO 8601 week column.3. Every date in the forms:
<c-date-field>(the button and the picker, after the box it fills) andcore/date_widgets.py:DateInput, andDateTimeInput, a date box with its calendar plus a nativetype="time"box, read back as theYYYY-MM-DDTHH:MMthedatetime-localbox posted. Fields, input formats and the "cannot end before it starts" checks are unchanged, a request posting the value whole still works,?on=prefills still work. The 28 date and 7 date-and-time fields of the applications, listings, capture, documents and CV forms use them. The interview's "was the time changed" check (#545) now reads the posted value through the widget.4. The calendar: the period's name opens a picker per view (month: months; week: week-selecting grid; day: day grid; agenda: range). It writes into the existing GET form and sends it, so the form stays the no-script path and both reach the same address. The agenda takes
?until=(its last day): absent means the thirty days as before (old links unchanged), reversed is swapped, capped at 366 days (AGENDA_MAX_DAYS); Previous/Next/Today step by and keep the length ("Next 7 days"); the title becomes "start to end"; the legend, the agenda's view link, the masthead search (SearchScope keep) and the file download carry it.5. The report and the table filters: the report's month box gets the month picker, its From/To boxes one range picker; each table date filter (header and narrow copies, ids kept) one range picker, which fires
changeonce on the second box, so htmx narrows the table once with both.A bug found on the way
Django's
DateInputwrites its value in the language's first input format,%d/%m/%Yin British English, and atype="date"box throws19/10/2026away: a stored date (closing date, CV dates, ...) was drawn as an empty box on every edit page in en-GB and other day-first languages. The new widget always writes ISO 8601. Regression test included (fails on the old widget).Deviations from the plan
core/date_widgets.py, notcore/widgets.py: that name is already the dashboard's widget registry.core/weeks.py(NUMBERED_WEEKS,numbers_weeks()), notWEEK_NUMBERS = Trueinconfig/formats/<lang>/formats.py: Django'sget_formatreads only format names it knows, and that package's docstring rules an invented one out.<c-date-field>draws the button and the picker, not the input: the box stays drawn by Django (or the page), and the component goes after it. This lets one component serve a form field, the report's pair and a filter's pair without wrapping their different markup.?on=YYYY-MM-01(the form's ownonbox), not?month=YYYY-MM; both draw the same month.Library check
bootstrap-datepicker was the issue's model for behaviour (
minViewMode, week selection, range), not a dependency: it needs jQuery and Bootstrap's CSS. Other candidates (flatpickr, vanilla-calendar-pro, Pikaday) bring their own styling and markup and would replace, not extend, the ARIA grid #928 already built and tested; extending it kept one component and no new vendored script.New strings for Weblate (msgids only, no msgstr)
Choose a month,Previous year,Next year,From %(start)s to %(end)s,From %(start)s: now choose the last day,Choose dates from a calendar,Time,%(start)s to %(end)s,Previous %(days)s day/Previous %(days)s days,Next %(days)s day/Next %(days)s days.Week of %(day)s,Week,Calendar,From,To,Dates you chooseare reused. The status sentences go throughblocktranslateso the catalogue holds%(day)sas Python's do, not an escaped%%(day)s.Tests
tests/test_date_pickers_933.py(component modes, first weekday on<html>, week numbering, both widgets including split/whole posts and the #545 check, the per-view pickers, agendauntildefault/cap/swap/round trip/steps, the report and the filters, the empty-box regression). Updated where behaviour changed:test_appointments.py,test_reminders_on_the_calendar.py(two boxes instead of one),test_theme.py(the<html>tag grew attributes),test_tables.py(unnamed controls post nothing),test_width.py(a picker's<table role="grid">is not a page table). Whole unit suite-n 2, without GeoNames data as in CI: 19,493 passed, 120 skipped, 1 xfailed, and one failure,test_width.py, fixed in the last commit and rerun alone (72 passed).-m e2e --browser chromium: newtests/e2e/test_date_pickers.py(17),test_calendar_views.py(21, with five new per-view tests),test_birth_picker.py(11),test_appointments.py(4),test_dated_things.py(13),test_application_filters.py(40),test_column_filters.py(26),test_accessibility.py(23),test_rendering_modes.py(4): all pass.test_application_filters.py's 320 px reflow check now skips controls inside a closed popover.A model (Claude) wrote this change under the contributor's direction; it was reviewed as a stranger's patch would be.
Wiki: a commit in
postulo.wiki(not pushed) describes the pickers under Tracking applications → Calendar.🤖 Generated with Claude Code
The period's name opened the same "Go to the day" box in all four views. With a script it now opens the picker that fits the view: the months of a year for the month, a day grid that chooses a whole week for the week, a day for the day, and a first and a last day for the agenda. The picker writes its choice into the GET form that was already there and sends it, so the form stays the way in with no script and both arrive at the same address. The button keeps `popovertarget` on that form; `data-picker` retargets it once the script runs. The agenda had no end day to choose, so a range picker had nothing to write. `?until=` is its last day now: - absent, the agenda runs the thirty days it always did, and every old address says what it said; thirty days named are the same as none; - an end before the start is swapped, and an end more than a year off (`AGENDA_MAX_DAYS`, 366) is brought back; - Previous, Next and Today step by the chosen length and keep it, and their names say it ("Next 7 days"); the title is "start to end"; - the legend, the agenda's view button, the search (SearchScope keeps `until`) and the file download carry it; the other views ignore it; - the no-script form gains a "To" box beside "From" in the agenda. Refs #933 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>ad783cec4afdc243067bfdc243067bb99131a08c