From cd0f256a726c01ff4e8e48b4c646a1d50bc005e7 Mon Sep 17 00:00:00 2001 From: Grant Fitzsimmons <37256050+grantfitzsimmons@users.noreply.github.com> Date: Fri, 17 Jan 2025 12:39:10 -0600 Subject: [PATCH 1/4] Create `patches` Django app --- specifyweb/patches/__init__.py | 0 specifyweb/patches/admin.py | 3 +++ specifyweb/patches/apps.py | 6 ++++++ specifyweb/patches/migrations/__init__.py | 0 specifyweb/patches/models.py | 3 +++ specifyweb/patches/tests.py | 3 +++ specifyweb/patches/views.py | 3 +++ specifyweb/settings/__init__.py | 1 + 8 files changed, 19 insertions(+) create mode 100644 specifyweb/patches/__init__.py create mode 100644 specifyweb/patches/admin.py create mode 100644 specifyweb/patches/apps.py create mode 100644 specifyweb/patches/migrations/__init__.py create mode 100644 specifyweb/patches/models.py create mode 100644 specifyweb/patches/tests.py create mode 100644 specifyweb/patches/views.py diff --git a/specifyweb/patches/__init__.py b/specifyweb/patches/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/specifyweb/patches/admin.py b/specifyweb/patches/admin.py new file mode 100644 index 00000000000..8c38f3f3dad --- /dev/null +++ b/specifyweb/patches/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/specifyweb/patches/apps.py b/specifyweb/patches/apps.py new file mode 100644 index 00000000000..303b063df40 --- /dev/null +++ b/specifyweb/patches/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class PatchesConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'specifyweb.patches' diff --git a/specifyweb/patches/migrations/__init__.py b/specifyweb/patches/migrations/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/specifyweb/patches/models.py b/specifyweb/patches/models.py new file mode 100644 index 00000000000..71a83623907 --- /dev/null +++ b/specifyweb/patches/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/specifyweb/patches/tests.py b/specifyweb/patches/tests.py new file mode 100644 index 00000000000..7ce503c2dd9 --- /dev/null +++ b/specifyweb/patches/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/specifyweb/patches/views.py b/specifyweb/patches/views.py new file mode 100644 index 00000000000..91ea44a218f --- /dev/null +++ b/specifyweb/patches/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/specifyweb/settings/__init__.py b/specifyweb/settings/__init__.py index 963f3914f5a..14bc5182735 100644 --- a/specifyweb/settings/__init__.py +++ b/specifyweb/settings/__init__.py @@ -222,6 +222,7 @@ def get_sa_db_url(db_name): 'specifyweb.attachment_gw', 'specifyweb.frontend', 'specifyweb.barvis', + 'specifyweb.patches', 'specifyweb.report_runner', 'specifyweb.interactions', 'specifyweb.workbench', From e216a22b1ef89a86842a207bd610d20f10842491 Mon Sep 17 00:00:00 2001 From: Grant Fitzsimmons <37256050+grantfitzsimmons@users.noreply.github.com> Date: Fri, 17 Jan 2025 12:39:12 -0600 Subject: [PATCH 2/4] Create 0001_restore_separators.py --- .../migrations/0001_restore_separators.py | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 specifyweb/patches/migrations/0001_restore_separators.py diff --git a/specifyweb/patches/migrations/0001_restore_separators.py b/specifyweb/patches/migrations/0001_restore_separators.py new file mode 100644 index 00000000000..363ee2d5055 --- /dev/null +++ b/specifyweb/patches/migrations/0001_restore_separators.py @@ -0,0 +1,25 @@ +# Generated by Django 3.2.15 on 2025-01-17 18:10 + +from django.db import migrations + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.RunSQL( + """ + UPDATE spappresourcedata spard + SET spard.data = REPLACE(spard.data, 'separator=""', 'separator="; "') + WHERE spard.SpAppResourceID IN ( + SELECT spar.SpAppResourceID + FROM spappresource spar + WHERE spar.Name = 'DataObjFormatters' + ); + """, + reverse_sql='' # This should not be reversed + ) + ] From c3d2761b53ea525900f5dec27a766a739dbf3f1d Mon Sep 17 00:00:00 2001 From: Grant Fitzsimmons <37256050+grantfitzsimmons@users.noreply.github.com> Date: Tue, 4 Feb 2025 20:40:03 +0000 Subject: [PATCH 3/4] Lint code with ESLint and Prettier Triggered by a0c1fdf25331c22d9759be15067c64eb7fdb2284 on branch refs/heads/issue-5154-patch --- .../Preferences/UserDefinitions.tsx | 7 +-- .../lib/components/QueryComboBox/index.tsx | 47 ++++++++++--------- .../RouterCommands/SwitchCollection.tsx | 4 +- 3 files changed, 31 insertions(+), 27 deletions(-) diff --git a/specifyweb/frontend/js_src/lib/components/Preferences/UserDefinitions.tsx b/specifyweb/frontend/js_src/lib/components/Preferences/UserDefinitions.tsx index 044bbd9fc25..78e891d0dff 100644 --- a/specifyweb/frontend/js_src/lib/components/Preferences/UserDefinitions.tsx +++ b/specifyweb/frontend/js_src/lib/components/Preferences/UserDefinitions.tsx @@ -65,9 +65,10 @@ const isDarkMode = ({ }: PreferencesVisibilityContext): boolean => isDarkMode || isRedirecting; // Navigator may not be defined in some environments, like non-browser environments -const altKeyName = typeof navigator !== 'undefined' && navigator?.userAgent?.includes('Mac') - ? 'Option' - : 'Alt'; +const altKeyName = + typeof navigator !== 'undefined' && navigator?.userAgent?.includes('Mac') + ? 'Option' + : 'Alt'; /** * Have to be careful as preferences may be used before schema is loaded diff --git a/specifyweb/frontend/js_src/lib/components/QueryComboBox/index.tsx b/specifyweb/frontend/js_src/lib/components/QueryComboBox/index.tsx index 81f98171382..76a9b506749 100644 --- a/specifyweb/frontend/js_src/lib/components/QueryComboBox/index.tsx +++ b/specifyweb/frontend/js_src/lib/components/QueryComboBox/index.tsx @@ -264,29 +264,32 @@ export function QueryComboBox({ (typeof typeSearch === 'object' ? typeSearch?.table : undefined) ?? field.relatedTable; - const [fetchedTreeDefinition] = useAsyncState( - React.useCallback(async () => { - if (resource?.specifyTable === tables.Determination) { - return resource.collection?.related?.specifyTable === tables.CollectionObject - ? (resource.collection?.related as SpecifyResource) - .rgetPromise('collectionObjectType') - .then( - ( - collectionObjectType: - | SpecifyResource - | undefined - ) => collectionObjectType?.get('taxonTreeDef') - ) - : undefined; - } else if (resource?.specifyTable === tables.Taxon) { - const definition = resource.get('definition') - const parentDefinition = (resource?.independentResources?.parent as SpecifyResource)?.get?.('definition'); - return definition || parentDefinition; + const [fetchedTreeDefinition] = useAsyncState( + React.useCallback(async () => { + if (resource?.specifyTable === tables.Determination) { + return resource.collection?.related?.specifyTable === + tables.CollectionObject + ? (resource.collection?.related as SpecifyResource) + .rgetPromise('collectionObjectType') + .then( + ( + collectionObjectType: + | SpecifyResource + | undefined + ) => collectionObjectType?.get('taxonTreeDef') + ) + : undefined; + } else if (resource?.specifyTable === tables.Taxon) { + const definition = resource.get('definition'); + const parentDefinition = ( + resource?.independentResources?.parent as SpecifyResource + )?.get?.('definition'); + return definition || parentDefinition; } - return undefined; - }, [resource, resource?.collection?.related?.get('collectionObjectType')]), - false - ); + return undefined; + }, [resource, resource?.collection?.related?.get('collectionObjectType')]), + false + ); // Tree Definition passed by a parent QCBX in the component tree const parentTreeDefinition = React.useContext(TreeDefinitionContext); diff --git a/specifyweb/frontend/js_src/lib/components/RouterCommands/SwitchCollection.tsx b/specifyweb/frontend/js_src/lib/components/RouterCommands/SwitchCollection.tsx index aeb54a2407e..ef201ec09d8 100644 --- a/specifyweb/frontend/js_src/lib/components/RouterCommands/SwitchCollection.tsx +++ b/specifyweb/frontend/js_src/lib/components/RouterCommands/SwitchCollection.tsx @@ -42,8 +42,8 @@ export function SwitchCollectionCommand(): null { body: collectionId!.toString(), errorMode: 'dismissible', }) - .then(clearAllCache) - .then(() => globalThis.location.replace(nextUrl)), + .then(clearAllCache) + .then(() => globalThis.location.replace(nextUrl)), [collectionId, nextUrl] ), true From 152de68164f1724b5841dc390ea95ebd1b1a96b5 Mon Sep 17 00:00:00 2001 From: Grant Fitzsimmons <37256050+grantfitzsimmons@users.noreply.github.com> Date: Mon, 10 Mar 2025 16:37:04 +0000 Subject: [PATCH 4/4] Lint code with ESLint and Prettier Triggered by 746f54ce4bf0d3f54351dc2e544f6780d1048f44 on branch refs/heads/issue-5154-patch --- .../js_src/lib/components/DataModel/resource.ts | 10 ++++++---- .../js_src/lib/components/Router/OverlayRoutes.tsx | 2 +- .../frontend/js_src/lib/components/WbUtils/index.tsx | 2 +- .../js_src/lib/components/WorkBench/WbValidation.tsx | 2 +- .../lib/components/WorkBench/batchEditHelpers.ts | 12 ++++++------ .../js_src/lib/components/WorkBench/hooks.ts | 2 +- .../js_src/lib/components/WorkBench/hotHelpers.ts | 4 ++-- .../js_src/lib/components/WorkBench/resultsParser.ts | 8 ++++---- 8 files changed, 22 insertions(+), 20 deletions(-) diff --git a/specifyweb/frontend/js_src/lib/components/DataModel/resource.ts b/specifyweb/frontend/js_src/lib/components/DataModel/resource.ts index f415260d004..d1d0e9fef0e 100644 --- a/specifyweb/frontend/js_src/lib/components/DataModel/resource.ts +++ b/specifyweb/frontend/js_src/lib/components/DataModel/resource.ts @@ -25,7 +25,7 @@ import type { import type { SpecifyResource } from './legacyTypes'; import { schema } from './schema'; import { serializeResource } from './serializers'; -import { SpecifyTable } from './specifyTable'; +import type { SpecifyTable } from './specifyTable'; import { genericTables, getTable, tables } from './tables'; import type { Tables } from './types'; import { getUniquenessRules } from './uniquenessRules'; @@ -343,9 +343,11 @@ export const exportsForTests = { }; setDevelopmentGlobal('_getUniqueFields', (): void => { - // Batch-editor clones records in independent-to-one no-match cases. It needs to be aware of the fields to not clone. It's fine if it doesn't respect user preferences (for now), but needs to be replicate - // front-end logic. So, the "fields to not clone" must be identical. This is done by storing them as a static file, which frontend and backend both access + a unit test to make sure the file is up-to-date. - // In the case where the user is really doesn't want to carry-over some fields, they can simply add those fields in batch-edit query (and then set them to null) so it handles general use case pretty well. + /* + * Batch-editor clones records in independent-to-one no-match cases. It needs to be aware of the fields to not clone. It's fine if it doesn't respect user preferences (for now), but needs to be replicate + * front-end logic. So, the "fields to not clone" must be identical. This is done by storing them as a static file, which frontend and backend both access + a unit test to make sure the file is up-to-date. + * In the case where the user is really doesn't want to carry-over some fields, they can simply add those fields in batch-edit query (and then set them to null) so it handles general use case pretty well. + */ const allTablesResult = Object.fromEntries( Object.values(tables).map((table) => [ table.name.toLowerCase(), diff --git a/specifyweb/frontend/js_src/lib/components/Router/OverlayRoutes.tsx b/specifyweb/frontend/js_src/lib/components/Router/OverlayRoutes.tsx index 6516db38c46..0dd82a1e8f0 100644 --- a/specifyweb/frontend/js_src/lib/components/Router/OverlayRoutes.tsx +++ b/specifyweb/frontend/js_src/lib/components/Router/OverlayRoutes.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { attachmentsText } from '../../localization/attachments'; +import { batchEditText } from '../../localization/batchEdit'; import { commonText } from '../../localization/common'; import { headerText } from '../../localization/header'; import { interactionsText } from '../../localization/interactions'; @@ -15,7 +16,6 @@ import { wbText } from '../../localization/workbench'; import type { RA } from '../../utils/types'; import { Redirect } from './Redirect'; import type { EnhancedRoute } from './RouterUtils'; -import { batchEditText } from '../../localization/batchEdit'; /* eslint-disable @typescript-eslint/promise-function-async */ /** diff --git a/specifyweb/frontend/js_src/lib/components/WbUtils/index.tsx b/specifyweb/frontend/js_src/lib/components/WbUtils/index.tsx index ebeac2923ff..fd89adca043 100644 --- a/specifyweb/frontend/js_src/lib/components/WbUtils/index.tsx +++ b/specifyweb/frontend/js_src/lib/components/WbUtils/index.tsx @@ -131,7 +131,7 @@ export function WbUtilsComponent({ /> { // Only show these cells if batch-edit - isUpdate === true && ( + isUpdate && ( <> >; -// just to make things manageable +// Just to make things manageable type RecordCountsKey = keyof Pick< UploadResult['UploadResult']['record_result'], 'Deleted' | 'MatchedAndChanged' | 'Updated' | 'Uploaded' diff --git a/specifyweb/frontend/js_src/lib/components/WorkBench/batchEditHelpers.ts b/specifyweb/frontend/js_src/lib/components/WorkBench/batchEditHelpers.ts index fa82ab2cfb0..8773fcd6533 100644 --- a/specifyweb/frontend/js_src/lib/components/WorkBench/batchEditHelpers.ts +++ b/specifyweb/frontend/js_src/lib/components/WorkBench/batchEditHelpers.ts @@ -1,5 +1,5 @@ -import { R, RA } from '../../utils/types'; -import { MappingPath } from '../WbPlanView/Mapper'; +import type { R, RA } from '../../utils/types'; +import type { MappingPath } from '../WbPlanView/Mapper'; import { getNumberFromToManyIndex, valueIsToManyIndex, @@ -12,7 +12,7 @@ export const BATCH_EDIT_NULL_RECORD = 'null_record'; export const BATCH_EDIT_KEY = 'batch_edit'; type BatchEditRecord = { - readonly id: typeof BATCH_EDIT_NULL_RECORD | number | undefined; + readonly id: number | typeof BATCH_EDIT_NULL_RECORD | undefined; readonly ordernumber: number | undefined; readonly version: number | undefined; }; @@ -34,13 +34,13 @@ export const isBatchEditNullRecord = ( // FEAT: Remove this if (valueIsTreeRank(node)) return false; - // it may actually not be a to-many + // It may actually not be a to-many const isToMany = rest[0] !== undefined && valueIsToManyIndex(rest[0]); - // batch-edit pack is strictly lower-case + // Batch-edit pack is strictly lower-case const lookUpNode = node.toLowerCase(); if (isToMany) { - // id starts with 1... + // Id starts with 1... const toManyId = getNumberFromToManyIndex(rest[0]) - 1; const toMany = batchEditPack?.to_many?.[lookUpNode]?.[toManyId]; return isBatchEditNullRecord(toMany, rest.slice(1)); diff --git a/specifyweb/frontend/js_src/lib/components/WorkBench/hooks.ts b/specifyweb/frontend/js_src/lib/components/WorkBench/hooks.ts index 9fcaa9c6461..a5ed8be0fab 100644 --- a/specifyweb/frontend/js_src/lib/components/WorkBench/hooks.ts +++ b/specifyweb/frontend/js_src/lib/components/WorkBench/hooks.ts @@ -14,9 +14,9 @@ import { overwriteReadOnly } from '../../utils/types'; import { sortFunction } from '../../utils/utils'; import { LoadingContext } from '../Core/Contexts'; import { schema } from '../DataModel/schema'; +import type { WbMeta } from './CellMeta'; import { getHotPlugin } from './handsontable'; import type { Workbench } from './WbView'; -import { WbMeta } from './CellMeta'; export function useHotHooks({ workbench, diff --git a/specifyweb/frontend/js_src/lib/components/WorkBench/hotHelpers.ts b/specifyweb/frontend/js_src/lib/components/WorkBench/hotHelpers.ts index e6a4eed9911..5bac1a6f206 100644 --- a/specifyweb/frontend/js_src/lib/components/WorkBench/hotHelpers.ts +++ b/specifyweb/frontend/js_src/lib/components/WorkBench/hotHelpers.ts @@ -1,8 +1,8 @@ import type Handsontable from 'handsontable'; import type { RA, RR, WritableArray } from '../../utils/types'; -import { WbMapping } from './mapping'; -import { Dataset } from '../WbPlanView/Wrapped'; +import type { Dataset } from '../WbPlanView/Wrapped'; +import type { WbMapping } from './mapping'; export function getSelectedRegions(hot: Handsontable): RA<{ readonly startRow: number; diff --git a/specifyweb/frontend/js_src/lib/components/WorkBench/resultsParser.ts b/specifyweb/frontend/js_src/lib/components/WorkBench/resultsParser.ts index 0f753275e73..e6dc78fb01e 100644 --- a/specifyweb/frontend/js_src/lib/components/WorkBench/resultsParser.ts +++ b/specifyweb/frontend/js_src/lib/components/WorkBench/resultsParser.ts @@ -154,18 +154,18 @@ type PropagatedFailure = State<'PropagatedFailure'>; type MatchedAndChanged = State<'MatchedAndChanged', Omit>; type RecordResultTypes = + | Deleted | FailedBusinessRule | Matched + | MatchedAndChanged | MatchedMultiple + | NoChange | NoMatch | NullRecord | ParseFailures | PropagatedFailure - | Uploaded | Updated - | NoChange - | Deleted - | MatchedAndChanged; + | Uploaded; // Records the specific result of attempting to upload a particular record type WbRecordResult = {