-
Couldn't load subscription status.
- Fork 40
Description
Currently, configuring global (database-wide) behaviors—such as auditing and date formatting—requires administrators to manually edit preferences app resources. This process is not user-friendly, is prone to syntax errors, and lacks discoverability for new users who are unaware these settings exist.
Note
This replaces #3169 and additional context there should be reviewed before implementation.
Creating a dedicated user interface for these settings, similar to the existing User Preferences panel, will significantly improve usability, reduce configuration errors, and lower the support burden. This feature will provide a centralized and intuitive location for Institution Administrators to manage all global, database-level preferences.
Requirements
- New User Interface: A new settings page, titled "Global Preferences," should be created to manage all preferences that apply to an entire database instance. The interface should be modeled after the existing
UserPreferencespage for consistency. - Menu Accessibility: This page should be accessible from the User Tools menu via a new menu item named "Global Preferences."
- Permissions:
- Access to the "Global Preferences" menu item and page should be granted only to users with the
Institution Adminrole, as these settings affect all collections and users in the database.
- Access to the "Global Preferences" menu item and page should be granted only to users with the
- Documentation:
- Links to relevant documentation for each preference will need to be added.
Migration
Existing preferences for each of these fields must be migrated from the existing preferences app resources at the global level of the database. This is necessary to not interrupt actions in the database (WorkBench uploads affected by custom date formats), auditing behavior, or currently configured options for thumbnail generation.
Preference Configuration
The following table outlines the settings that should be configurable from the new "Global Preferences" interface.
General
| Preference Title | Internal Name | Type | Default Value | Source | Description |
|---|---|---|---|---|---|
| Enable Audit Log | auditing.do_audits |
Checkbox | true |
remotePrefs.ts |
Globally enables or disables the Audit Log feature, which tracks record creation, deletion, and modifications. |
| Log Field-Level Changes | auditing.audit_field_updates |
Checkbox | true |
remotePrefs.ts |
If the Audit Log is enabled, this controls whether changes to individual field values are recorded. |
| Full Date Format | ui.formatting.scrdateformat |
Text | yyyy-MM-dd |
remotePrefs.ts |
Sets the display format for full dates (e.g., 2025-09-23). This should be a combobox with all supported formats. |
| Month/Year Date Format | ui.formatting.scrmonthformat |
Text | YYYY-MM |
remotePrefs.ts |
Sets the display format for dates where only the month and year are relevant (e.g., 2025-09). This should be a combobox with all supported formats. |
| Attachment Thumbnail Size | attachment.preview_size |
Integer | 256 |
remotePrefs.ts |
The size in pixels (e.g., 256) for generated thumbnails for attachment previews. |
Notes from @maxpatiiuk in #3169 (comment):
100% agree with @grantfitzsimmons
Migrate all as best as we can one time on specify update, and then keep them as separate as possible
BUT, take this migration as a time to:
- rethink and standardize the naming (the naming used in remote/global prefs right now is totally inconsistent) - probably would make most sense to reuse the naming convention used in user preferences, and to follow as similar structure to that as possible
- improve existing prefs during the transition (improve names, make values clearer, etc - whatever you see can be improved, now is the best time)
- think through all the other prefs we could introduce so that we can introduce a lot at once (I did that with user preferences) - to discover the edge cases early on and to figure out which categories/subcategories you should have (it's easier to see what categories you should have when you have 30 prefs rather than 3) so that you don't have to add additional migrations in the future to change that
- DATES - The date format is specified in the prefs right now. This is the moment to migrate from moment.js to a newer date library.
luxonanddate-fnsseem to be the two most popular at the moment - evaluate these to see what works best for us. The syntax for specifying the date format would change. Fortunately, there is only a limited number of date formats we currently support (probably the same as sp6) -- that makes migration easier (just create a dictionary of mappings from old date to new equivalent date syntax in the new library)specify7/specifyweb/stored_queries/format.py
Lines 273 to 352 in 6bc3785
MYSQL_TO_YEAR = { "%m %d %y": "%y", "%m %d %Y": "%Y", "%m-%d-%y": "%y", "%m-%d-%Y": "%Y", "%m.%d.%y": "%y", "%m.%d.%Y": "%Y", "%m/%d/%y": "%y", "%m/%d/%Y": "%Y", "%d %m %y": "%y", "%d %m %Y": "%Y", "%d %b %Y": "%Y", "%d-%m-%y": "%y", "%d-%m-%Y": "%Y", "%d-%b-%Y": "%Y", "%d.%m.%y": "%y", "%d.%m.%Y": "%Y", "%d.%b.%Y": "%Y", "%d/%m/%y": "%y", "%d/%m/%Y": "%Y", "%d/%b/%Y": "%Y", "%Y %m %d": "%Y", "%Y-%m-%d": "%Y", "%Y.%m.%d": "%Y", "%Y/%m/%d": "%Y", } MYSQL_TO_MONTH = { "%m %d %y": "%m %y", "%m %d %Y": "%m %Y", "%m-%d-%y": "%m-%y", "%m-%d-%Y": "%m-%Y", "%m.%d.%y": "%m.%y", "%m.%d.%Y": "%m.%Y", "%m/%d/%y": "%m/%y", "%m/%d/%Y": "%m/%Y", "%d %m %y": "%m %y", "%d %m %Y": "%m %Y", "%d %b %Y": "%b %Y", "%d-%m-%y": "%m-%y", "%d-%m-%Y": "%m-%Y", "%d-%b-%Y": "%b-%Y", "%d.%m.%y": "%m.%y", "%d.%m.%Y": "%m.%Y", "%d.%b.%Y": "%b.%Y", "%d/%m/%y": "%m/%y", "%d/%m/%Y": "%m/%Y", "%d/%b/%Y": "%b/%Y", "%Y %m %d": "%Y %m", "%Y-%m-%d": "%Y-%m", "%Y.%m.%d": "%Y.%m", "%Y/%m/%d": "%Y/%m", } LDLM_TO_MYSQL = { "MM dd yy": "%m %d %y", "MM dd yyyy": "%m %d %Y", "MM-dd-yy": "%m-%d-%y", "MM-dd-yyyy": "%m-%d-%Y", "MM.dd.yy": "%m.%d.%y", "MM.dd.yyyy": "%m.%d.%Y", "MM/dd/yy": "%m/%d/%y", "MM/dd/yyyy": "%m/%d/%Y", "dd MM yy": "%d %m %y", "dd MM yyyy": "%d %m %Y", "dd MMM yyyy": "%d %b %Y", "dd-MM-yy": "%d-%m-%y", "dd-MM-yyyy": "%d-%m-%Y", "dd-MMM-yyyy": "%d-%b-%Y", "dd.MM.yy": "%d.%m.%y", "dd.MM.yyyy": "%d.%m.%Y", "dd.MMM.yyyy": "%d.%b.%Y", "dd/MM/yy": "%d/%m/%y", "dd/MM/yyyy": "%d/%m/%Y", "dd/MMM/yyy": "%d/%b/%Y", "yyyy MM dd": "%Y %m %d", "yyyy-MM-dd": "%Y-%m-%d", "yyyy.MM.dd": "%Y.%m.%d", "yyyy/MM/dd": "%Y/%m/%d", }
- Also, in the UI also do a better job of pointing out that the date pickers will use the date format based on current system date format, but that will still show correct date in the database, and query results and reports and workbench. If they do want to see equivalent date formats, they should disable the "Use Accessible Date Picker" setting. I.e put the "Use Accessible Date Picker" pref right below the date format, and add a handy description/discourse page link.
side note
we are using day.js right now instead of moment.js, but day.js was built to be backwards compatible with moment.js to serve as a drop-in replacement. But, I have been quite unhappy with day.js so far:
- First, they advertise themself as being much leaner than moment.js - but they do that by packaging some features into a separate bundle - we happen to need those features (date parsing and date building from a date format string) - as a result, our bundle size increased after migrating from moment.js to day.js.
- Secondly, and much more importantly, day.js has bugs - I had to add clumsy workarounds for their bugs -
. At the time, even the latest version of the library had these bugs. The biggest one is the fact that the European date format is not supported!!! I could not belive it when I saw it, but that seems to be the case (see the ticket for that - [Sp.77] - when using European date format, aggregated dates are broken for any day over the 12th of the month #1908)specify7/specifyweb/frontend/js_src/lib/components/FormPlugins/PartialDateUi.tsx
Lines 70 to 112 in 6bc3785
// TODO: [REFACTOR] migrate from day.js to date-fns /** * Several bugs have been discovered in day.js in the process of testing * Specify 7. The bugs are present in the latest stable version as of this * writing (1.11.4). * Day.js 2.0 is currently in beta. Need to wait for 2.0 to get released or * migrate to date-fns. */ function fixDayJsBugs( precision: PartialDatePrecision, value: string ): ReturnType<typeof dayjs> | undefined { if (precision === 'month-year') return unsafeParseMonthYear(value); else if (precision === 'full') return unsafeParseFullDate(value); else return undefined; } /** * An ugly workaround for a bug in day.js where any date in the MM/YYYY format is * parsed as an invalid date. */ function unsafeParseMonthYear( value: string ): ReturnType<typeof dayjs> | undefined { const parsed = /(\d{2})\D(\d{4})/.exec(value)?.slice(1); if (parsed === undefined) return undefined; const [month, year] = parsed.map(f.unary(Number.parseInt)); return dayjs(new Date(year, month - 1)); } /** * An ugly workaround for a bug in day.js where any date in the DD/MM/YYY format * is parsed as an invalid date. */ function unsafeParseFullDate( value: string ): ReturnType<typeof dayjs> | undefined { if (fullDateFormat().toUpperCase() !== 'DD/MM/YYYY') return; const parsed = /(\d{2})\D(\d{2})\D(\d{4})/.exec(value)?.slice(1); if (parsed === undefined) return undefined; const [day, month, year] = parsed.map(f.unary(Number.parseInt)); return dayjs(new Date(year, month - 1, day)); }