From 7b1b4d9356f7911ba36187bdca725b6931f25242 Mon Sep 17 00:00:00 2001 From: Caroline D <108160931+CarolineDenis@users.noreply.github.com> Date: Wed, 11 Sep 2024 10:10:59 -0700 Subject: [PATCH 01/34] Add isLoanable only for loans when choosing record set --- .../Interactions/InteractionDialog.tsx | 10 ++++- .../lib/components/Interactions/helpers.ts | 11 ++++- specifyweb/interactions/views.py | 42 ++++++++++++------- 3 files changed, 44 insertions(+), 19 deletions(-) diff --git a/specifyweb/frontend/js_src/lib/components/Interactions/InteractionDialog.tsx b/specifyweb/frontend/js_src/lib/components/Interactions/InteractionDialog.tsx index 9f19bfa7597..8e73162dfad 100644 --- a/specifyweb/frontend/js_src/lib/components/Interactions/InteractionDialog.tsx +++ b/specifyweb/frontend/js_src/lib/components/Interactions/InteractionDialog.tsx @@ -106,6 +106,8 @@ export function InteractionDialog({ const loading = React.useContext(LoadingContext); + const isLoan = actionTable.name === 'Loan'; + function handleProceed( recordSet: SerializedResource | undefined ): void { @@ -133,7 +135,7 @@ export function InteractionDialog({ ); else if (typeof recordSet === 'object') loading( - getPrepsAvailableForLoanRs(recordSet.id).then((data) => + getPrepsAvailableForLoanRs(recordSet.id, isLoan).then((data) => availablePrepsReady(undefined, data) ) ); @@ -141,7 +143,11 @@ export function InteractionDialog({ loading( (catalogNumbers.length === 0 ? Promise.resolve([]) - : getPrepsAvailableForLoanCoIds('CatalogNumber', catalogNumbers) + : getPrepsAvailableForLoanCoIds( + 'CatalogNumber', + catalogNumbers, + isLoan + ) ).then((data) => availablePrepsReady(catalogNumbers, data)) ); } diff --git a/specifyweb/frontend/js_src/lib/components/Interactions/helpers.ts b/specifyweb/frontend/js_src/lib/components/Interactions/helpers.ts index 4ddd41d0c35..21ea5352abc 100644 --- a/specifyweb/frontend/js_src/lib/components/Interactions/helpers.ts +++ b/specifyweb/frontend/js_src/lib/components/Interactions/helpers.ts @@ -53,17 +53,23 @@ export type PreparationRow = readonly [ amountAvailable: string ]; -export const getPrepsAvailableForLoanRs = async (recordSetId: number) => +export const getPrepsAvailableForLoanRs = async ( + recordSetId: number, + isLoan: boolean +) => ajax>( `/interactions/preparations_available_rs/${recordSetId}/`, { + method: 'POST', headers: { Accept: 'application/json' }, + body: formData({ isLoan }), } ).then(({ data }) => data); export const getPrepsAvailableForLoanCoIds = async ( idField: string, - collectionObjectIds: RA + collectionObjectIds: RA, + isLoan: boolean ) => ajax>('/interactions/preparations_available_ids/', { method: 'POST', @@ -71,6 +77,7 @@ export const getPrepsAvailableForLoanCoIds = async ( body: formData({ id_fld: idField, co_ids: collectionObjectIds, + isLoan, }), }).then(({ data }) => data); diff --git a/specifyweb/interactions/views.py b/specifyweb/interactions/views.py index b9e98087e08..96e97f85516 100644 --- a/specifyweb/interactions/views.py +++ b/specifyweb/interactions/views.py @@ -14,12 +14,15 @@ from specifyweb.specify.views import login_maybe_required +@require_POST @login_maybe_required -@require_GET def preps_available_rs(request, recordset_id): - "Returns a list of preparations that are loanable? based on the CO recordset ." + "Returns a list of preparations that are loanable?(for loan) based on the CO recordset ." cursor = connection.cursor() - cursor.execute(""" + + isLoan = request.POST.get('isLoan', 'false').lower() == 'true' + + sql = """ SELECT co.catalognumber, co.collectionobjectid AS co_id, t.fullname, @@ -47,20 +50,22 @@ def preps_available_rs(request, recordset_id): ON d.collectionobjectid = co.collectionobjectid LEFT JOIN taxon t ON t.taxonid = d.taxonid - WHERE pt.isloanable - AND p.collectionmemberid = %s + WHERE p.collectionmemberid = %s AND ( d.iscurrent OR d.determinationid IS NULL ) AND p.collectionobjectid IN (SELECT recordid FROM recordsetitem WHERE recordsetid = %s) - GROUP BY 1, - 2, - 3, - 4, - 5 - ORDER BY 1; - """, [request.specify_collection.id, recordset_id]) + """ + + # Add `pt.isloanable` if `isLoan` + if isLoan: + sql += " AND pt.isloanable" + + sql += " GROUP BY 1,2,3,4,5 ORDER BY 1;" + + cursor.execute(sql, [request.specify_collection.id, recordset_id]) + rows = cursor.fetchall() return http.HttpResponse(toJson(rows), content_type='application/json') @@ -68,7 +73,7 @@ def preps_available_rs(request, recordset_id): @require_POST @login_maybe_required def preps_available_ids(request): - """Returns a list of preparations that are loanable? based on + """Returns a list of preparations that are loanable? (for loans) based on a list of collection object ids passed in the 'co_ids' POST parameter as a JSON list. """ @@ -81,6 +86,8 @@ def preps_available_ids(request): co_ids = json.loads(request.POST['co_ids']) + isLoan = request.POST.get('isLoan', 'false').lower() == 'true' + sql = """ select co.CatalogNumber, co.collectionObjectId, t.FullName, t.taxonId, p.preparationid, pt.name, p.countAmt, sum(lp.quantity-lp.quantityreturned) Loaned, sum(gp.quantity) Gifted, sum(ep.quantity) Exchanged, @@ -93,12 +100,17 @@ def preps_available_ids(request): inner join preptype pt on pt.preptypeid = p.preptypeid left join determination d on d.CollectionObjectID = co.CollectionObjectID left join taxon t on t.TaxonID = d.TaxonID - where pt.isloanable and p.collectionmemberid = %s + where p.collectionmemberid = %s and (d.IsCurrent or d.DeterminationID is null) and co.{id_fld} in ({params}) - group by 1,2,3,4,5 order by 1; """.format(id_fld=id_fld, params=",".join("%s" for __ in co_ids)) + # Add `pt.isloanable` if `isLoan` + if isLoan: + sql += " and pt.isloanable" + + sql += " group by 1,2,3,4,5 order by 1;" + cursor = connection.cursor() cursor.execute(sql, [int(request.specify_collection.id)] + co_ids) rows = cursor.fetchall() From 8ba3d8261627f06694b19e0f4542dbb4b8b93622 Mon Sep 17 00:00:00 2001 From: Caroline D <108160931+CarolineDenis@users.noreply.github.com> Date: Wed, 11 Sep 2024 11:19:36 -0700 Subject: [PATCH 02/34] Fix snapshot --- .../js_src/lib/components/Syncer/__tests__/index.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specifyweb/frontend/js_src/lib/components/Syncer/__tests__/index.test.ts b/specifyweb/frontend/js_src/lib/components/Syncer/__tests__/index.test.ts index 89b362c28c0..bb7abf22a9c 100644 --- a/specifyweb/frontend/js_src/lib/components/Syncer/__tests__/index.test.ts +++ b/specifyweb/frontend/js_src/lib/components/Syncer/__tests__/index.test.ts @@ -94,8 +94,8 @@ test('Editing Data Object Formatter', () => { - - + + " `); From 445bd2169f067b73813e3a50e2f51027de28253f Mon Sep 17 00:00:00 2001 From: Sharad S Date: Mon, 7 Oct 2024 17:03:01 -0400 Subject: [PATCH 03/34] Fix upload plan parser --- .../components/WbPlanView/uploadPlanBuilder.ts | 17 ++++++++++++----- .../components/WbPlanView/uploadPlanParser.ts | 11 +++++------ 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/specifyweb/frontend/js_src/lib/components/WbPlanView/uploadPlanBuilder.ts b/specifyweb/frontend/js_src/lib/components/WbPlanView/uploadPlanBuilder.ts index 3082316a7e4..89220dc5ec8 100644 --- a/specifyweb/frontend/js_src/lib/components/WbPlanView/uploadPlanBuilder.ts +++ b/specifyweb/frontend/js_src/lib/components/WbPlanView/uploadPlanBuilder.ts @@ -80,12 +80,11 @@ const toTreeRecordVariety = (lines: RA): TreeRecord => { ({ definition }) => definition.name === treeName )?.definition.id; + // Key can be either or ~> return [ - treeId === undefined + treeId === undefined || treeName === undefined ? rankName - : `${ - treeName === undefined ? '' : treeName + RANK_KEY_DELIMITER - }${rankName}`, + : formatTreeRankKey(rankName, treeName), { treeNodeCols: toTreeRecordRanks(mappedFields), treeId, @@ -214,5 +213,13 @@ const indexMappings = ( ) ); -// Delimiter used for rank name keys i.e: ~> +// Delimiter used for rank name keys i.e: ~> export const RANK_KEY_DELIMITER = '~>'; + +/** + * Returns a formatted tree rank name along with its tree name: (e.x Taxonomy~>Kingdom) + * Used for generating unique key names in the upload plan when there are multiple trees with the same rank name. + * NOTE: Opposite of uploadPlanParser.ts > getRankNameFromKey() + */ +const formatTreeRankKey = (rankName: string, treeName: string): string => + `${treeName}${RANK_KEY_DELIMITER}${rankName}`; \ No newline at end of file diff --git a/specifyweb/frontend/js_src/lib/components/WbPlanView/uploadPlanParser.ts b/specifyweb/frontend/js_src/lib/components/WbPlanView/uploadPlanParser.ts index f5220b43d2e..ebcbed09e33 100644 --- a/specifyweb/frontend/js_src/lib/components/WbPlanView/uploadPlanParser.ts +++ b/specifyweb/frontend/js_src/lib/components/WbPlanView/uploadPlanParser.ts @@ -83,7 +83,7 @@ const parseTree = ( getTreeDefinitions('Taxon', 'all').length > 1 ? [resolveTreeId(rankData.treeId)] : []), - formatTreeRank(getRankNameWithoutTreeId(rankName)), + formatTreeRank(getRankNameFromKey(rankName)), ] ) ); @@ -215,9 +215,8 @@ export function parseUploadPlan(uploadPlan: UploadPlan): { } /** - * Returns the tree rank name after stripping its tree id: (e.x Kingdom~>1 => Kingdom) - * NOTE: Does not consider whether rankName is formatted with $ or not. - * Opposite of uploadPlanBuilder.ts > formatTreeRankWithTreeId() + * Returns the tree rank name from a tree upload plan key (ex: ~> => ) + * NOTE: Opposite of uploadPlanBuilder.ts > formatTreeRankKey() */ -const getRankNameWithoutTreeId = (rankName: string): string => - rankName.split(RANK_KEY_DELIMITER)[0]; +const getRankNameFromKey = (rankName: string): string => + rankName.split(RANK_KEY_DELIMITER).at(-1)!; From 3c84d50f7358138f95dd30462ec017bde343df37 Mon Sep 17 00:00:00 2001 From: Sharad S Date: Mon, 7 Oct 2024 21:06:46 +0000 Subject: [PATCH 04/34] Lint code with ESLint and Prettier Triggered by 445bd2169f067b73813e3a50e2f51027de28253f on branch refs/heads/issue-5305 --- .../js_src/lib/components/WbPlanView/uploadPlanBuilder.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specifyweb/frontend/js_src/lib/components/WbPlanView/uploadPlanBuilder.ts b/specifyweb/frontend/js_src/lib/components/WbPlanView/uploadPlanBuilder.ts index 89220dc5ec8..72ca16707f4 100644 --- a/specifyweb/frontend/js_src/lib/components/WbPlanView/uploadPlanBuilder.ts +++ b/specifyweb/frontend/js_src/lib/components/WbPlanView/uploadPlanBuilder.ts @@ -222,4 +222,4 @@ export const RANK_KEY_DELIMITER = '~>'; * NOTE: Opposite of uploadPlanParser.ts > getRankNameFromKey() */ const formatTreeRankKey = (rankName: string, treeName: string): string => - `${treeName}${RANK_KEY_DELIMITER}${rankName}`; \ No newline at end of file + `${treeName}${RANK_KEY_DELIMITER}${rankName}`; From af943c41c577093bf2e11da13a028af8b056b2bf Mon Sep 17 00:00:00 2001 From: Sharad S Date: Tue, 8 Oct 2024 14:23:28 -0400 Subject: [PATCH 05/34] Change data model field names --- .../js_src/lib/components/DataModel/types.ts | 229 +++++++++--------- specifyweb/specify/datamodel.py | 22 +- 2 files changed, 130 insertions(+), 121 deletions(-) diff --git a/specifyweb/frontend/js_src/lib/components/DataModel/types.ts b/specifyweb/frontend/js_src/lib/components/DataModel/types.ts index 31a0a227385..327626f5963 100644 --- a/specifyweb/frontend/js_src/lib/components/DataModel/types.ts +++ b/specifyweb/frontend/js_src/lib/components/DataModel/types.ts @@ -229,24 +229,33 @@ export type Tables = { readonly CollectionObjectGroup: CollectionObjectGroup; readonly CollectionObjectGroupJoin: CollectionObjectGroupJoin; readonly CollectionObjectGroupType: CollectionObjectGroupType; + readonly AbsoluteAge: AbsoluteAge; + readonly RelativeAge: RelativeAge; + readonly AbsoluteAgeAttachment: AbsoluteAgeAttachment; + readonly RelativeAgeAttachment: RelativeAgeAttachment; + readonly AbsoluteAgeCitation: AbsoluteAgeCitation; + readonly RelativeAgeCitation: RelativeAgeCitation; + readonly TectonicUnitTreeDef: TectonicUnitTreeDef; + readonly TectonicUnitTreeDefItem: TectonicUnitTreeDefItem; + readonly TectonicUnit: TectonicUnit; }; export type Accession = { readonly tableName: 'Accession'; readonly fields: { - readonly accessionNumber: string; readonly accessionCondition: string | null; - readonly dateAccessioned: string | null; + readonly accessionNumber: string; readonly actualTotalCountAmt: number | null; readonly collectionObjectCount: number | null; + readonly dateAccessioned: string | null; readonly dateAcknowledged: string | null; - readonly remarks: string | null; + readonly dateReceived: string | null; readonly integer1: number | null; readonly integer2: number | null; readonly integer3: number | null; readonly number1: number | null; readonly number2: number | null; readonly preparationCount: number | null; - readonly dateReceived: string | null; + readonly remarks: string | null; readonly status: string | null; readonly text1: string | null; readonly text2: string | null; @@ -452,10 +461,10 @@ export type Agent = { readonly initials: string | null; readonly integer1: number | null; readonly integer2: number | null; + readonly interests: string | null; readonly jobTitle: string | null; readonly lastName: string | null; readonly middleInitial: string | null; - readonly interests: string | null; readonly remarks: string | null; readonly suffix: string | null; readonly text1: string | null; @@ -488,8 +497,8 @@ export type Agent = { readonly agentAttachments: RA; readonly agentGeographies: RA; readonly agentSpecialties: RA; - readonly identifiers: RA; readonly groups: RA; + readonly identifiers: RA; readonly variants: RA; }; readonly toManyIndependent: { @@ -920,13 +929,13 @@ export type BorrowMaterial = { readonly tableName: 'BorrowMaterial'; readonly fields: { readonly collectionMemberId: number; + readonly description: string | null; readonly inComments: string | null; readonly materialNumber: string; readonly outComments: string | null; readonly quantity: number | null; readonly quantityResolved: number | null; readonly quantityReturned: number | null; - readonly description: string | null; readonly text1: string | null; readonly text2: string | null; readonly timestampCreated: string; @@ -968,22 +977,24 @@ export type BorrowReturnMaterial = { export type CollectingEvent = { readonly tableName: 'CollectingEvent'; readonly fields: { - readonly startDate: string | null; + readonly verbatimDate: string | null; + readonly remarks: string | null; readonly endDate: string | null; readonly endDatePrecision: number | null; readonly endDateVerbatim: string | null; readonly endTime: number | null; - readonly stationFieldNumber: string | null; - readonly method: string | null; readonly guid: string | null; readonly integer1: number | null; readonly integer2: number | null; - readonly remarks: string | null; + readonly stationFieldNumber: string | null; + readonly verbatimLocality: string | null; + readonly method: string | null; readonly reservedInteger3: number | null; readonly reservedInteger4: number | null; readonly reservedText1: string | null; readonly reservedText2: string | null; readonly sgrStatus: number | null; + readonly startDate: string | null; readonly startDatePrecision: number | null; readonly startDateVerbatim: string | null; readonly startTime: number | null; @@ -1001,8 +1012,6 @@ export type CollectingEvent = { readonly timestampCreated: string; readonly timestampModified: string | null; readonly uniqueIdentifier: string | null; - readonly verbatimDate: string | null; - readonly verbatimLocality: string | null; readonly version: number | null; readonly visibility: number | null; }; @@ -1070,10 +1079,6 @@ export type CollectingEventAttr = { export type CollectingEventAttribute = { readonly tableName: 'CollectingEventAttribute'; readonly fields: { - readonly text8: string | null; - readonly text5: string | null; - readonly text4: string | null; - readonly text9: string | null; readonly integer1: number | null; readonly integer10: number | null; readonly integer2: number | null; @@ -1084,11 +1089,11 @@ export type CollectingEventAttribute = { readonly integer7: number | null; readonly integer8: number | null; readonly integer9: number | null; - readonly number12: number | null; - readonly number13: number | null; readonly number1: number | null; readonly number10: number | null; readonly number11: number | null; + readonly number12: number | null; + readonly number13: number | null; readonly number2: number | null; readonly number3: number | null; readonly number4: number | null; @@ -1098,22 +1103,26 @@ export type CollectingEventAttribute = { readonly number8: number | null; readonly number9: number | null; readonly remarks: string | null; - readonly text6: string | null; readonly text1: string | null; readonly text10: string | null; readonly text11: string | null; + readonly text12: string | null; readonly text13: string | null; readonly text14: string | null; readonly text15: string | null; readonly text16: string | null; readonly text17: string | null; readonly text2: string | null; + readonly text3: string | null; + readonly text4: string | null; + readonly text5: string | null; + readonly text6: string | null; readonly text7: string | null; + readonly text8: string | null; + readonly text9: string | null; readonly timestampCreated: string; readonly timestampModified: string | null; - readonly text12: string | null; readonly version: number | null; - readonly text3: string | null; readonly yesNo1: boolean | null; readonly yesNo2: boolean | null; readonly yesNo3: boolean | null; @@ -1154,7 +1163,6 @@ export type CollectingTrip = { readonly tableName: 'CollectingTrip'; readonly fields: { readonly cruise: string | null; - readonly text2: string | null; readonly date1: string | null; readonly date1Precision: number | null; readonly date2: string | null; @@ -1172,6 +1180,8 @@ export type CollectingTrip = { readonly startDatePrecision: number | null; readonly startDateVerbatim: string | null; readonly startTime: number | null; + readonly text1: string | null; + readonly text2: string | null; readonly text3: string | null; readonly text4: string | null; readonly text5: string | null; @@ -1183,7 +1193,6 @@ export type CollectingTrip = { readonly timestampModified: string | null; readonly collectingTripName: string | null; readonly version: number | null; - readonly text1: string | null; readonly vessel: string | null; readonly yesNo1: boolean | null; readonly yesNo2: boolean | null; @@ -1359,16 +1368,19 @@ export type Collection = { export type CollectionObject = { readonly tableName: 'CollectionObject'; readonly fields: { + readonly yesNo1: boolean | null; readonly actualTotalCountAmt: number | null; readonly age: number | null; + readonly altCatalogNumber: string | null; readonly availability: string | null; - readonly catalogNumber: string | null; readonly catalogedDate: string | null; readonly catalogedDatePrecision: number | null; readonly catalogedDateVerbatim: string | null; + readonly catalogNumber: string | null; + readonly remarks: string | null; readonly collectionMemberId: number; readonly countAmt: number | null; - readonly reservedText: string | null; + readonly timestampCreated: string; readonly timestampModified: string | null; readonly date1: string | null; readonly date1Precision: number | null; @@ -1381,9 +1393,10 @@ export type CollectionObject = { readonly guid: string | null; readonly integer1: number | null; readonly integer2: number | null; - readonly text2: string | null; readonly inventoryDate: string | null; readonly inventoryDatePrecision: number | null; + readonly text2: string | null; + readonly fieldNumber: string | null; readonly modifier: string | null; readonly name: string | null; readonly notifications: string | null; @@ -1392,16 +1405,15 @@ export type CollectionObject = { readonly number2: number | null; readonly objectCondition: string | null; readonly ocr: string | null; - readonly altCatalogNumber: string | null; + readonly text1: string | null; readonly projectNumber: string | null; - readonly remarks: string | null; readonly reservedInteger3: number | null; readonly reservedInteger4: number | null; + readonly reservedText: string | null; readonly reservedText2: string | null; readonly reservedText3: string | null; readonly restrictions: string | null; readonly sgrStatus: number | null; - readonly text1: string | null; readonly description: string | null; readonly text3: string | null; readonly text4: string | null; @@ -1409,14 +1421,11 @@ export type CollectionObject = { readonly text6: string | null; readonly text7: string | null; readonly text8: string | null; - readonly timestampCreated: string; readonly totalCountAmt: number | null; readonly totalValue: number | null; readonly uniqueIdentifier: string | null; readonly version: number | null; readonly visibility: number | null; - readonly fieldNumber: string | null; - readonly yesNo1: boolean | null; readonly yesNo2: boolean | null; readonly yesNo3: boolean | null; readonly yesNo4: boolean | null; @@ -1431,17 +1440,17 @@ export type CollectionObject = { readonly agent1: Agent | null; readonly appraisal: Appraisal | null; readonly cataloger: Agent | null; + readonly collectingEvent: CollectingEvent | null; readonly collection: Collection; readonly collectionObjectType: CollectionObjectType; readonly container: Container | null; readonly containerOwner: Container | null; readonly createdByAgent: Agent | null; readonly currentDetermination: Determination | null; - readonly modifiedByAgent: Agent | null; readonly embargoAuthority: Agent | null; - readonly collectingEvent: CollectingEvent | null; readonly fieldNotebookPage: FieldNotebookPage | null; readonly inventorizedBy: Agent | null; + readonly modifiedByAgent: Agent | null; readonly paleoContext: PaleoContext | null; readonly visibilitySetBy: SpecifyUser | null; }; @@ -1524,11 +1533,11 @@ export type CollectionObjectAttribute = { readonly integer7: number | null; readonly integer8: number | null; readonly integer9: number | null; - readonly number12: number | null; - readonly number13: number | null; readonly number1: number | null; readonly number10: number | null; readonly number11: number | null; + readonly number12: number | null; + readonly number13: number | null; readonly number14: number | null; readonly number15: number | null; readonly number16: number | null; @@ -1566,19 +1575,20 @@ export type CollectionObjectAttribute = { readonly number7: number | null; readonly number8: number | null; readonly number9: number | null; - readonly text13: string | null; - readonly text14: string | null; - readonly text1: string | null; readonly positionState: string | null; - readonly text10: string | null; readonly remarks: string | null; - readonly text8: string | null; + readonly text1: string | null; + readonly text10: string | null; readonly text11: string | null; + readonly text12: string | null; + readonly text13: string | null; + readonly text14: string | null; readonly text15: string | null; readonly text16: string | null; readonly text17: string | null; readonly text18: string | null; readonly text19: string | null; + readonly text2: string | null; readonly text20: string | null; readonly text21: string | null; readonly text22: string | null; @@ -1605,13 +1615,12 @@ export type CollectionObjectAttribute = { readonly text5: string | null; readonly text6: string | null; readonly text7: string | null; + readonly text8: string | null; readonly text9: string | null; readonly timestampCreated: string; readonly timestampModified: string | null; - readonly text12: string | null; readonly topDistance: number | null; readonly version: number | null; - readonly text2: string | null; readonly yesNo1: boolean | null; readonly yesNo10: boolean | null; readonly yesNo11: boolean | null; @@ -1649,10 +1658,10 @@ export type CollectionObjectCitation = { readonly fields: { readonly collectionMemberId: number; readonly figureNumber: string | null; + readonly remarks: string | null; readonly isFigured: boolean | null; readonly pageNumber: string | null; readonly plateNumber: string | null; - readonly remarks: string | null; readonly timestampCreated: string; readonly timestampModified: string | null; readonly version: number | null; @@ -2185,9 +2194,7 @@ export type DNASequence = { readonly compT: number | null; readonly extractionDate: string | null; readonly extractionDatePrecision: number | null; - readonly text2: string | null; readonly genbankAccessionNumber: string | null; - readonly text1: string | null; readonly geneSequence: string | null; readonly moleculeType: string | null; readonly number1: number | null; @@ -2197,6 +2204,8 @@ export type DNASequence = { readonly sequenceDate: string | null; readonly sequenceDatePrecision: number | null; readonly targetMarker: string | null; + readonly text1: string | null; + readonly text2: string | null; readonly text3: string | null; readonly timestampCreated: string; readonly timestampModified: string | null; @@ -2289,8 +2298,8 @@ export type DNASequencingRun = { readonly runByAgent: Agent | null; }; readonly toManyDependent: { - readonly citations: RA; readonly attachments: RA; + readonly citations: RA; }; readonly toManyIndependent: RR; }; @@ -2452,13 +2461,11 @@ export type Determination = { readonly addendum: string | null; readonly alternateName: string | null; readonly collectionMemberId: number; - readonly confidence: string | null; readonly isCurrent: boolean; readonly determinedDate: string | null; readonly determinedDatePrecision: number | null; readonly featureOrBasis: string | null; readonly guid: string | null; - readonly yesNo1: boolean | null; readonly integer1: number | null; readonly integer2: number | null; readonly integer3: number | null; @@ -2471,10 +2478,11 @@ export type Determination = { readonly number3: number | null; readonly number4: number | null; readonly number5: number | null; + readonly text1: string | null; readonly qualifier: string | null; + readonly confidence: string | null; readonly remarks: string | null; readonly subSpQualifier: string | null; - readonly text1: string | null; readonly text2: string | null; readonly text3: string | null; readonly text4: string | null; @@ -2487,6 +2495,7 @@ export type Determination = { readonly typeStatusName: string | null; readonly varQualifier: string | null; readonly version: number | null; + readonly yesNo1: boolean | null; readonly yesNo2: boolean | null; readonly yesNo3: boolean | null; readonly yesNo4: boolean | null; @@ -2513,9 +2522,9 @@ export type DeterminationCitation = { readonly collectionMemberId: number; readonly figureNumber: string | null; readonly isFigured: boolean | null; + readonly remarks: string | null; readonly pageNumber: string | null; readonly plateNumber: string | null; - readonly remarks: string | null; readonly timestampCreated: string; readonly timestampModified: string | null; readonly version: number | null; @@ -3082,12 +3091,13 @@ export type FundingAgent = { export type GeoCoordDetail = { readonly tableName: 'GeoCoordDetail'; readonly fields: { + readonly validation: string | null; + readonly source: string | null; readonly errorPolygon: string | null; readonly geoRefAccuracy: number | null; readonly geoRefAccuracyUnits: string | null; readonly geoRefCompiledDate: string | null; readonly geoRefDetDate: string | null; - readonly geoRefDetRef: string | null; readonly geoRefRemarks: string | null; readonly geoRefVerificationStatus: string | null; readonly integer1: number | null; @@ -3106,7 +3116,7 @@ export type GeoCoordDetail = { readonly number5: number | null; readonly originalCoordSystem: string | null; readonly protocol: string | null; - readonly source: string | null; + readonly geoRefDetRef: string | null; readonly text1: string | null; readonly text2: string | null; readonly text3: string | null; @@ -3115,7 +3125,6 @@ export type GeoCoordDetail = { readonly timestampCreated: string; readonly timestampModified: string | null; readonly uncertaintyPolygon: string | null; - readonly validation: string | null; readonly version: number | null; readonly yesNo1: boolean | null; readonly yesNo2: boolean | null; @@ -3337,8 +3346,8 @@ export type Gift = { readonly srcTaxonomy: string | null; readonly specialConditions: string | null; readonly status: string | null; - readonly text2: string | null; readonly text1: string | null; + readonly text2: string | null; readonly text3: string | null; readonly text4: string | null; readonly text5: string | null; @@ -3707,7 +3716,6 @@ export type Loan = { readonly dateClosed: string | null; readonly dateReceived: string | null; readonly yesNo1: boolean | null; - readonly text2: string | null; readonly integer1: number | null; readonly integer2: number | null; readonly integer3: number | null; @@ -3729,6 +3737,7 @@ export type Loan = { readonly specialConditions: string | null; readonly status: string | null; readonly text1: string | null; + readonly text2: string | null; readonly text3: string | null; readonly text4: string | null; readonly text5: string | null; @@ -3854,22 +3863,26 @@ export type LoanReturnPreparation = { export type Locality = { readonly tableName: 'Locality'; readonly fields: { + readonly timestampCreated: string; + readonly timestampModified: string | null; readonly datum: string | null; readonly elevationAccuracy: number | null; + readonly elevationMethod: string | null; readonly gml: string | null; readonly guid: string | null; readonly latLongMethod: string | null; readonly lat1text: string | null; readonly lat2text: string | null; - readonly latLongAccuracy: number | null; readonly latLongType: string | null; readonly latitude1: number | null; readonly latitude2: number | null; + readonly latLongAccuracy: number | null; readonly localityName: string; readonly long1text: string | null; readonly long2text: string | null; readonly longitude1: number | null; readonly longitude2: number | null; + readonly text1: string | null; readonly maxElevation: number | null; readonly minElevation: number | null; readonly namedPlace: string | null; @@ -3880,20 +3893,16 @@ export type Locality = { readonly sgrStatus: number | null; readonly shortName: string | null; readonly srcLatLongUnit: number; - readonly text1: string | null; readonly text2: string | null; readonly text3: string | null; readonly text4: string | null; readonly text5: string | null; - readonly timestampCreated: string; - readonly timestampModified: string | null; readonly uniqueIdentifier: string | null; readonly verbatimElevation: string | null; readonly verbatimLatitude: string | null; readonly verbatimLongitude: string | null; readonly version: number | null; readonly visibility: number | null; - readonly elevationMethod: string | null; readonly yesNo1: boolean | null; readonly yesNo2: boolean | null; readonly yesNo3: boolean | null; @@ -3966,7 +3975,6 @@ export type LocalityCitation = { export type LocalityDetail = { readonly tableName: 'LocalityDetail'; readonly fields: { - readonly baseMeridian: string | null; readonly drainage: string | null; readonly endDepth: number | null; readonly endDepthUnit: string | null; @@ -3975,6 +3983,7 @@ export type LocalityDetail = { readonly hucCode: string | null; readonly island: string | null; readonly islandGroup: string | null; + readonly text1: string | null; readonly mgrsZone: string | null; readonly nationalParkName: string | null; readonly number1: number | null; @@ -3991,7 +4000,7 @@ export type LocalityDetail = { readonly startDepth: number | null; readonly startDepthUnit: string | null; readonly startDepthVerbatim: string | null; - readonly text1: string | null; + readonly baseMeridian: string | null; readonly text2: string | null; readonly text3: string | null; readonly text4: string | null; @@ -4168,6 +4177,7 @@ export type PaleoContext = { readonly tableName: 'PaleoContext'; readonly fields: { readonly text1: string | null; + readonly text2: string | null; readonly paleoContextName: string | null; readonly number1: number | null; readonly number2: number | null; @@ -4175,7 +4185,6 @@ export type PaleoContext = { readonly number4: number | null; readonly number5: number | null; readonly remarks: string | null; - readonly text2: string | null; readonly text3: string | null; readonly text4: string | null; readonly text5: string | null; @@ -4374,6 +4383,7 @@ export type Preparation = { readonly date4Precision: number | null; readonly description: string | null; readonly guid: string | null; + readonly text1: string | null; readonly integer1: number | null; readonly integer2: number | null; readonly isOnLoan: boolean | null; @@ -4391,6 +4401,7 @@ export type Preparation = { readonly text11: string | null; readonly text12: string | null; readonly text13: string | null; + readonly text2: string | null; readonly text3: string | null; readonly text4: string | null; readonly text5: string | null; @@ -4400,10 +4411,8 @@ export type Preparation = { readonly text9: string | null; readonly timestampCreated: string; readonly timestampModified: string | null; - readonly text1: string | null; - readonly yesNo1: boolean | null; readonly version: number | null; - readonly text2: string | null; + readonly yesNo1: boolean | null; readonly yesNo2: boolean | null; readonly yesNo3: boolean | null; }; @@ -4780,10 +4789,7 @@ export type RecordSetItem = { export type ReferenceWork = { readonly tableName: 'ReferenceWork'; readonly fields: { - readonly text1: string | null; - readonly workDate: string | null; readonly doi: string | null; - readonly text2: string | null; readonly guid: string | null; readonly isPublished: boolean | null; readonly isbn: string | null; @@ -4794,6 +4800,8 @@ export type ReferenceWork = { readonly placeOfPublication: string | null; readonly publisher: string | null; readonly remarks: string | null; + readonly text1: string | null; + readonly text2: string | null; readonly timestampCreated: string; readonly timestampModified: string | null; readonly title: string; @@ -4802,6 +4810,7 @@ export type ReferenceWork = { readonly url: string | null; readonly version: number | null; readonly volume: string | null; + readonly workDate: string | null; readonly yesNo1: boolean | null; readonly yesNo2: boolean | null; }; @@ -4899,9 +4908,9 @@ export type RepositoryAgreementAttachment = { export type Shipment = { readonly tableName: 'Shipment'; readonly fields: { - readonly numberOfPackages: number | null; readonly insuredForAmount: string | null; readonly shipmentMethod: string | null; + readonly numberOfPackages: number | null; readonly number1: number | null; readonly number2: number | null; readonly remarks: string | null; @@ -5615,6 +5624,7 @@ export type Taxon = { readonly colStatus: string | null; readonly commonName: string | null; readonly cultivarName: string | null; + readonly environmentalProtectionStatus: string | null; readonly esaStatus: string | null; readonly fullName: string | null; readonly groupNumber: string | null; @@ -5638,7 +5648,6 @@ export type Taxon = { readonly number3: number | null; readonly number4: number | null; readonly number5: number | null; - readonly environmentalProtectionStatus: string | null; readonly rankId: number; readonly remarks: string | null; readonly source: string | null; @@ -6463,8 +6472,8 @@ export type CollectionObjectType = { readonly text1: string | null; readonly text2: string | null; readonly text3: string | null; - readonly timestampcreated: string; - readonly timestampmodified: string | null; + readonly timestampCreated: string; + readonly timestampModified: string | null; readonly version: number | null; }; readonly toOneDependent: RR; @@ -6493,8 +6502,8 @@ export type CollectionObjectGroup = { readonly text1: string | null; readonly text2: string | null; readonly text3: string | null; - readonly timestampcreated: string; - readonly timestampmodified: string | null; + readonly timestampCreated: string; + readonly timestampModified: string | null; readonly version: number | null; readonly yesno1: boolean | null; readonly yesno2: boolean | null; @@ -6502,14 +6511,14 @@ export type CollectionObjectGroup = { }; readonly toOneDependent: RR; readonly toOneIndependent: { - readonly cogtype: CollectionObjectGroupType; + readonly cogType: CollectionObjectGroupType; readonly collection: Collection | null; readonly createdByAgent: Agent | null; readonly modifiedByAgent: Agent | null; }; readonly toManyDependent: { readonly cojo: RA; - readonly parentcojos: RA; + readonly parentCojos: RA; }; readonly toManyIndependent: RR; }; @@ -6519,14 +6528,14 @@ export type CollectionObjectGroupJoin = { readonly integer1: number | null; readonly integer2: number | null; readonly integer3: number | null; - readonly isprimary: boolean; - readonly issubstrate: boolean; + readonly isPrimary: boolean; + readonly isSubstrate: boolean; readonly precedence: number; readonly text1: string | null; readonly text2: string | null; readonly text3: string | null; - readonly timestampcreated: string; - readonly timestampmodified: string | null; + readonly timestampCreated: string; + readonly timestampModified: string | null; readonly version: number | null; readonly yesno1: boolean | null; readonly yesno2: boolean | null; @@ -6534,9 +6543,9 @@ export type CollectionObjectGroupJoin = { }; readonly toOneDependent: RR; readonly toOneIndependent: { - readonly childco: CollectionObject | null; - readonly childcog: CollectionObjectGroup | null; - readonly parentcog: CollectionObjectGroup; + readonly childCo: CollectionObject | null; + readonly childCog: CollectionObjectGroup | null; + readonly parentCog: CollectionObjectGroup; }; readonly toManyDependent: RR; readonly toManyIndependent: RR; @@ -6545,8 +6554,8 @@ export type CollectionObjectGroupType = { readonly tableName: 'CollectionObjectGroupType'; readonly fields: { readonly name: string; - readonly timestampcreated: string; - readonly timestampmodified: string | null; + readonly timestampCreated: string; + readonly timestampModified: string | null; readonly type: string; readonly version: number | null; }; @@ -6575,10 +6584,10 @@ export type AbsoluteAge = { readonly remarks: string | null; readonly text1: string | null; readonly text2: string | null; - readonly yesno1: boolean | null; - readonly yesno2: boolean | null; readonly timestampCreated: string; readonly timestampModified: string | null; + readonly yesno1: boolean | null; + readonly yesno2: boolean | null; }; readonly toOneDependent: RR; readonly toOneIndependent: { @@ -6588,32 +6597,32 @@ export type AbsoluteAge = { readonly modifiedByAgent: Agent | null; }; readonly toManyDependent: { - readonly absoluteAgeAttachments: RA; + readonly absoluteAgeAttachments: RA; }; readonly toManyIndependent: RR; }; export type RelativeAge = { readonly tableName: 'RelativeAge'; readonly fields: { - readonly ageRype: string | null; + readonly ageType: string | null; readonly ageUncertainty: number | null; readonly collectionDate: string | null; readonly date1: string | null; readonly date2: string | null; readonly datingMethod: string | null; readonly datingMethodRemarks: string | null; - readonly relativeAgePeriod: number | null; readonly number1: number | null; readonly number2: number | null; + readonly relativeAgePeriod: number | null; readonly remarks: string | null; readonly text1: string | null; readonly text2: string | null; - readonly yesno1: boolean | null; - readonly yesno2: boolean | null; readonly timestampCreated: string; readonly timestampModified: string | null; readonly verbatimName: string | null; readonly verbatimPeriod: string | null; + readonly yesno1: boolean | null; + readonly yesno2: boolean | null; }; readonly toOneDependent: RR; readonly toOneIndependent: { @@ -6626,7 +6635,7 @@ export type RelativeAge = { readonly modifiedByAgent: Agent | null; }; readonly toManyDependent: { - readonly relativeAgeAttachments: RA; + readonly relativeAgeAttachments: RA; }; readonly toManyIndependent: RR; }; @@ -6639,9 +6648,7 @@ export type AbsoluteAgeAttachment = { readonly timestampModified: string | null; readonly version: number | null; }; - readonly toOneDependent: { - readonly attachment: Attachment; - }; + readonly toOneDependent: { readonly attachment: Attachment }; readonly toOneIndependent: { readonly absoluteAge: AbsoluteAge; readonly collectionMember: Collection; @@ -6660,9 +6667,7 @@ export type RelativeAgeAttachment = { readonly timestampModified: string | null; readonly version: number | null; }; - readonly toOneDependent: { - readonly attachment: Attachment; - }; + readonly toOneDependent: { readonly attachment: Attachment }; readonly toOneIndependent: { readonly collectionMember: Collection; readonly createdByAgent: Agent | null; @@ -6731,11 +6736,13 @@ export type TectonicUnitTreeDef = { readonly toOneDependent: RR; readonly toOneIndependent: { readonly createdByAgent: Agent | null; - readonly modifiedByAgent: Agent | null; readonly discipline: Discipline; + readonly modifiedByAgent: Agent | null; }; - readonly toManyDependent: RR; - readonly toManyIndependent: RR; + readonly toManyDependent: { + readonly treeDefItems: RA; + }; + readonly toManyIndependent: { readonly treeEntries: RA }; }; export type TectonicUnitTreeDefItem = { readonly tableName: 'TectonicUnitTreeDefItem'; @@ -6744,7 +6751,7 @@ export type TectonicUnitTreeDefItem = { readonly isEnforced: boolean | null; readonly isInFullName: boolean | null; readonly name: string; - readonly rankId: number | null; + readonly rankId: number; readonly remarks: string | null; readonly textAfter: string | null; readonly textBefore: string | null; @@ -6755,10 +6762,10 @@ export type TectonicUnitTreeDefItem = { }; readonly toOneDependent: RR; readonly toOneIndependent: { - readonly createdByAgent: Agent | null; + readonly createdbyagent: Agent | null; readonly modifiedByAgent: Agent | null; - readonly parentItem: TectonicUnitTreeDefItem | null; - readonly tectonicUnitTreeDef: TectonicUnitTreeDef; + readonly parent: TectonicUnitTreeDefItem | null; + readonly treeDef: TectonicUnitTreeDef; }; readonly toManyDependent: RR; readonly toManyIndependent: { @@ -6791,10 +6798,10 @@ export type TectonicUnit = { readonly toOneIndependent: { readonly acceptedTectonicUnit: TectonicUnit | null; readonly createdByAgent: Agent | null; + readonly definition: TectonicUnitTreeDef; + readonly definitionItem: TectonicUnitTreeDefItem; readonly modifiedByAgent: Agent | null; readonly parent: TectonicUnit; - readonly tectonicUnitTreeDef: TectonicUnitTreeDef; - readonly tectonicUnitTreeDefItem: TectonicUnitTreeDefItem; }; readonly toManyDependent: RR; readonly toManyIndependent: RR; diff --git a/specifyweb/specify/datamodel.py b/specifyweb/specify/datamodel.py index 33ca80b446d..2156126355a 100644 --- a/specifyweb/specify/datamodel.py +++ b/specifyweb/specify/datamodel.py @@ -8607,7 +8607,9 @@ relationships=[ Relationship(name='createdByAgent', type='many-to-one', required=False, relatedModelName='Agent', column='CreatedByAgentID'), Relationship(name='discipline', type='many-to-one', column='DisciplineID',required=True, relatedModelName='Discipline', otherSideName='tectonicUnitTreeDefs'), - Relationship(name='modifiedByAgent', type='many-to-one', required=False, relatedModelName='Agent', column='ModifiedByAgentID') + Relationship(name='modifiedByAgent', type='many-to-one', required=False, relatedModelName='Agent', column='ModifiedByAgentID'), + Relationship(name='treeDefItems', type='one-to-many',required=False, relatedModelName='TectonicUnitTreeDefItem', otherSideName='treeDef', dependent=True), + Relationship(name='treeEntries', type='one-to-many',required=False, relatedModelName='TectonicUnit', otherSideName='definition') ], fieldAliases=[ @@ -8627,7 +8629,7 @@ Field(name='isEnforced', column='IsEnforced', indexed=False, unique=False, required=False, type='java.lang.Boolean'), Field(name='isInFullName', column='IsInFullName', indexed=False, unique=False, required=False, type='java.lang.Boolean'), Field(name='name', column='Name', indexed=False, unique=False, required=True, type='java.lang.String', length=255), - Field(name='rankId', column='RankID', indexed=False, unique=False, required=False, type='java.lang.Integer'), + Field(name='rankId', column='RankID', indexed=False, unique=False, required=True, type='java.lang.Integer'), Field(name='remarks', column='Remarks', indexed=False, unique=False, required=False, type='text'), Field(name='textAfter', column='TextAfter', indexed=False, unique=False, required=False, type='java.lang.String', length=255), Field(name='textBefore', column='TextBefore', indexed=False, unique=False, required=False, type='java.lang.String', length=255), @@ -8640,13 +8642,13 @@ ], relationships=[ - Relationship(name='children', type='one-to-many',required=False, relatedModelName='TectonicUnitTreeDefItem', otherSideName='parentitem'), + Relationship(name='children', type='one-to-many',required=False, relatedModelName='TectonicUnitTreeDefItem', otherSideName='parent'), Relationship(name='createdbyagent', type='many-to-one', required=False, relatedModelName='Agent', column='CreatedByAgentID'), Relationship(name='modifiedByAgent', type='many-to-one', required=False, relatedModelName='Agent', column='ModifiedByAgentID'), - Relationship(name='parentItem', type='many-to-one', required=False, relatedModelName='TectonicUnitTreeDefItem', column='ParentItemID', otherSideName='children'), - Relationship(name='tectonicUnitTreeDef', type='many-to-one', required=True, relatedModelName='TectonicUnitTreeDef', column='TectonicUnitTreeDefID', - otherSideName='tectonicUnitTreeDefItems'), - Relationship(name='tectonicUnits', type='one-to-many',required=False, relatedModelName='TectonicUnit', otherSideName='definitionItem') + Relationship(name='parent', type='many-to-one', required=False, relatedModelName='TectonicUnitTreeDefItem', column='ParentItemID', otherSideName='children'), + Relationship(name='treeDef', type='many-to-one', required=True, relatedModelName='TectonicUnitTreeDef', column='TectonicUnitTreeDefID', + otherSideName='treeDefItems'), + Relationship(name='treeEntries', type='one-to-many',required=False, relatedModelName='TectonicUnit', otherSideName='definitionItem') ], fieldAliases=[ @@ -8687,9 +8689,9 @@ Relationship(name='acceptedTectonicUnit', type='many-to-one', required=False, relatedModelName='TectonicUnit', column='AcceptedID'), Relationship(name='createdByAgent', type='many-to-one', required=False, relatedModelName='Agent', column='CreatedByAgentID'), Relationship(name='modifiedByAgent', type='many-to-one', required=False, relatedModelName='Agent', column='ModifiedByAgentID'), - Relationship(name='parent', type='many-to-one', required=False, relatedModelName='TectonicUnit', column='ParentID'), - Relationship(name='tectonicUnitTreeDef', type='many-to-one', required=True, relatedModelName='TectonicUnitTreeDef', column='TectonicUnitTreeDefID', otherSideName='tectonicUnits'), - Relationship(name='tectonicUnitTreeDefItem', type='many-to-one', required=True, relatedModelName='TectonicUnitTreeDefItem', column='TectonicUnitTreeDefItemID', otherSideName='tectonicUnits'), + Relationship(name='parent', type='many-to-one', required=True, relatedModelName='TectonicUnit', column='ParentID'), + Relationship(name='definition', type='many-to-one', required=True, relatedModelName='TectonicUnitTreeDef', column='TectonicUnitTreeDefID', otherSideName='treeEntries'), + Relationship(name='definitionItem', type='many-to-one', required=True, relatedModelName='TectonicUnitTreeDefItem', column='TectonicUnitTreeDefItemID', otherSideName='treeEntries'), ], fieldAliases=[ From ec809a138ef20ccd3c432d48c6161c1d3f08616d Mon Sep 17 00:00:00 2001 From: Sharad S Date: Tue, 8 Oct 2024 14:46:19 -0400 Subject: [PATCH 06/34] add tectonicTreeUnit along with other trees --- .../lib/components/Forms/parentTables.ts | 2 +- .../components/InitialContext/remotePrefs.ts | 19 +++++++++++++++++++ .../components/InitialContext/treeRanks.ts | 3 ++- .../lib/components/QueryBuilder/fromTree.ts | 3 +++ .../js_src/lib/components/TreeView/Tree.tsx | 1 + 5 files changed, 26 insertions(+), 2 deletions(-) diff --git a/specifyweb/frontend/js_src/lib/components/Forms/parentTables.ts b/specifyweb/frontend/js_src/lib/components/Forms/parentTables.ts index 172b03af809..d7dc69bbd9d 100644 --- a/specifyweb/frontend/js_src/lib/components/Forms/parentTables.ts +++ b/specifyweb/frontend/js_src/lib/components/Forms/parentTables.ts @@ -71,7 +71,7 @@ const overrides: { CommonNameTx: 'taxon', BorrowReturnMaterial: 'borrowMaterial', CollectionObject: undefined, - CollectionObjectGroupJoin: 'parentcog', + CollectionObjectGroupJoin: 'parentCog', CollectionRelationship: undefined, Collector: 'collectingEvent', DNASequencingRun: 'dnaSequence', diff --git a/specifyweb/frontend/js_src/lib/components/InitialContext/remotePrefs.ts b/specifyweb/frontend/js_src/lib/components/InitialContext/remotePrefs.ts index c9fdd21a35d..d90d72ad737 100644 --- a/specifyweb/frontend/js_src/lib/components/InitialContext/remotePrefs.ts +++ b/specifyweb/frontend/js_src/lib/components/InitialContext/remotePrefs.ts @@ -156,6 +156,12 @@ export const remotePrefsDefinitions = f.store( formatters: [formatter.trim], isLegacy: true, }, + 'TectonicUnit.treeview_sort_field': { + description: 'Sort order for nodes in the tree viewer', + defaultValue: 'name', + formatters: [formatter.trim], + isLegacy: true, + }, 'TreeEditor.Rank.Threshold.GeologicTimePeriod': { description: 'Show Collection Object count only for nodes with RankID >= than this value', @@ -191,6 +197,13 @@ export const remotePrefsDefinitions = f.store( parser: 'java.lang.Long', isLegacy: true, }, + 'TreeEditor.Rank.Threshold.TectonicUnit': { + description: + 'Show Collection Object count only for nodes with RankID >= than this value', + defaultValue: 99_999, + parser: 'java.lang.Long', + isLegacy: true, + }, /* * This pref was implemented in Specify 7 in https://github.com/specify/specify7/pull/2818 @@ -266,6 +279,12 @@ export const remotePrefsDefinitions = f.store( parser: 'java.lang.Boolean', isLegacy: false, }, + 'sp7.allow_adding_child_to_synonymized_parent.TectonicUnit': { + description: 'Allowed to add children to synopsized TectonicUnit records', + defaultValue: false, + parser: 'java.lang.Boolean', + isLegacy: false, + }, // This is actually stored in Global Prefs: /* * 'AUDIT_LIFESPAN_MONTHS': { diff --git a/specifyweb/frontend/js_src/lib/components/InitialContext/treeRanks.ts b/specifyweb/frontend/js_src/lib/components/InitialContext/treeRanks.ts index 29c84c9da1c..3ded51fad8d 100644 --- a/specifyweb/frontend/js_src/lib/components/InitialContext/treeRanks.ts +++ b/specifyweb/frontend/js_src/lib/components/InitialContext/treeRanks.ts @@ -39,7 +39,8 @@ let treeDefinitions: TreeInformation = undefined!; */ const commonTrees = ['Geography', 'Storage', 'Taxon'] as const; const treesForPaleo = ['GeologicTimePeriod', 'LithoStrat'] as const; -export const allTrees = [...commonTrees, ...treesForPaleo] as const; +const treesForGeo = ['TectonicUnit'] as const; +export const allTrees = [...commonTrees, ...treesForPaleo, ...treesForGeo] as const; /* * Until discipline information is loaded, assume all trees are appropriate in * this discipline diff --git a/specifyweb/frontend/js_src/lib/components/QueryBuilder/fromTree.ts b/specifyweb/frontend/js_src/lib/components/QueryBuilder/fromTree.ts index fe93ffb1147..e5e36e932a9 100644 --- a/specifyweb/frontend/js_src/lib/components/QueryBuilder/fromTree.ts +++ b/specifyweb/frontend/js_src/lib/components/QueryBuilder/fromTree.ts @@ -152,6 +152,9 @@ const defaultFields: RR< : []), ]; }, + TectonicUnit(nodeId, rankName) { + + } }; async function fetchPaleoPath(): Promise { diff --git a/specifyweb/frontend/js_src/lib/components/TreeView/Tree.tsx b/specifyweb/frontend/js_src/lib/components/TreeView/Tree.tsx index 3cefb2a34b5..14ea75fcaee 100644 --- a/specifyweb/frontend/js_src/lib/components/TreeView/Tree.tsx +++ b/specifyweb/frontend/js_src/lib/components/TreeView/Tree.tsx @@ -31,6 +31,7 @@ const treeToPref = { Storage: 'storage', GeologicTimePeriod: 'geologicTimePeriod', LithoStrat: 'lithoStrat', + TectonicTreeUnit: 'techtonicTreeUnit' } as const; export function Tree< From 0d07ffac3701734d78c86fe6f211f27ada961f19 Mon Sep 17 00:00:00 2001 From: Sharad S Date: Tue, 8 Oct 2024 14:54:58 -0400 Subject: [PATCH 07/34] Fix typo --- specifyweb/frontend/js_src/lib/components/TreeView/Tree.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specifyweb/frontend/js_src/lib/components/TreeView/Tree.tsx b/specifyweb/frontend/js_src/lib/components/TreeView/Tree.tsx index 14ea75fcaee..683a4845877 100644 --- a/specifyweb/frontend/js_src/lib/components/TreeView/Tree.tsx +++ b/specifyweb/frontend/js_src/lib/components/TreeView/Tree.tsx @@ -31,7 +31,7 @@ const treeToPref = { Storage: 'storage', GeologicTimePeriod: 'geologicTimePeriod', LithoStrat: 'lithoStrat', - TectonicTreeUnit: 'techtonicTreeUnit' + TectonicTreeUnit: 'tectonicUnit' } as const; export function Tree< From 0eb5459b699a22c53426247397b74ad796d6c65b Mon Sep 17 00:00:00 2001 From: Sharad S Date: Tue, 8 Oct 2024 15:04:20 -0400 Subject: [PATCH 08/34] add permission policy for tectonic unit --- .../js_src/lib/components/Permissions/definitions.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/specifyweb/frontend/js_src/lib/components/Permissions/definitions.ts b/specifyweb/frontend/js_src/lib/components/Permissions/definitions.ts index 24db5e23b60..68bca10b572 100644 --- a/specifyweb/frontend/js_src/lib/components/Permissions/definitions.ts +++ b/specifyweb/frontend/js_src/lib/components/Permissions/definitions.ts @@ -66,6 +66,13 @@ export const operationPolicies = { 'repair', ], '/tree/edit/taxon': ['merge', 'move', 'synonymize', 'desynonymize', 'repair'], + '/tree/edit/tectonicunit': [ + 'merge', + 'move', + 'synonymize', + 'desynonymize', + 'repair', + ], '/workbench/dataset': [ 'create', 'update', From 9233183213cb3d4691372283e584966b91905c2c Mon Sep 17 00:00:00 2001 From: Sharad S Date: Tue, 8 Oct 2024 15:10:33 -0400 Subject: [PATCH 09/34] Add user prefs for tectonic unit --- .../Preferences/UserDefinitions.tsx | 26 +++++++++++++++++++ .../js_src/lib/components/TreeView/Tree.tsx | 2 +- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/specifyweb/frontend/js_src/lib/components/Preferences/UserDefinitions.tsx b/specifyweb/frontend/js_src/lib/components/Preferences/UserDefinitions.tsx index 973a772d447..328e786e3be 100644 --- a/specifyweb/frontend/js_src/lib/components/Preferences/UserDefinitions.tsx +++ b/specifyweb/frontend/js_src/lib/components/Preferences/UserDefinitions.tsx @@ -1492,6 +1492,27 @@ export const userPreferenceDefinitions = { }), }, }, + tectonicUnit: { + title: '_TectonicUnit' as LocalizedString, + items: { + treeAccentColor: definePref({ + title: preferencesText.treeAccentColor(), + requiresReload: false, + visible: true, + defaultValue: '#f79245', + renderer: ColorPickerPreferenceItem, + container: 'label', + }), + synonymColor: definePref({ + title: preferencesText.synonymColor(), + requiresReload: false, + visible: true, + defaultValue: '#dc2626', + renderer: ColorPickerPreferenceItem, + container: 'label', + }), + }, + }, }, }, queryBuilder: { @@ -2006,6 +2027,11 @@ import('../DataModel/tables') 'title', getField(tables.LithoStrat, 'name').label ); + overwriteReadOnly( + trees.tectonicUnit, + 'title', + getField(tables.TectonicUnit, 'name').label + ); overwriteReadOnly( userPreferenceDefinitions.form.subCategories.recordSet, 'title', diff --git a/specifyweb/frontend/js_src/lib/components/TreeView/Tree.tsx b/specifyweb/frontend/js_src/lib/components/TreeView/Tree.tsx index 683a4845877..bec182f9eca 100644 --- a/specifyweb/frontend/js_src/lib/components/TreeView/Tree.tsx +++ b/specifyweb/frontend/js_src/lib/components/TreeView/Tree.tsx @@ -31,7 +31,7 @@ const treeToPref = { Storage: 'storage', GeologicTimePeriod: 'geologicTimePeriod', LithoStrat: 'lithoStrat', - TectonicTreeUnit: 'tectonicUnit' + TectonicUnit: 'tectonicUnit' } as const; export function Tree< From e7c232023c3ed81bf9a115a79f0e528289fc380e Mon Sep 17 00:00:00 2001 From: Sharad S Date: Tue, 8 Oct 2024 15:23:58 -0400 Subject: [PATCH 10/34] add placeholder for query field function --- .../frontend/js_src/lib/components/QueryBuilder/fromTree.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/specifyweb/frontend/js_src/lib/components/QueryBuilder/fromTree.ts b/specifyweb/frontend/js_src/lib/components/QueryBuilder/fromTree.ts index e5e36e932a9..e87c6a5353c 100644 --- a/specifyweb/frontend/js_src/lib/components/QueryBuilder/fromTree.ts +++ b/specifyweb/frontend/js_src/lib/components/QueryBuilder/fromTree.ts @@ -152,9 +152,9 @@ const defaultFields: RR< : []), ]; }, - TectonicUnit(nodeId, rankName) { - - } + TectonicUnit: async (_nodeId, _rankName) => { + return [makeField('catalogNumber', {})]; + }, }; async function fetchPaleoPath(): Promise { From 0745b0c4cc003cdd10dcce3b70d0b38a1d304f04 Mon Sep 17 00:00:00 2001 From: Sharad S Date: Tue, 8 Oct 2024 15:53:34 -0400 Subject: [PATCH 11/34] add placeholders for fromTree --- .../components/QueryBuilder/__tests__/fromTree.test.ts | 10 ++++++++-- .../js_src/lib/components/QueryBuilder/fromTree.ts | 10 +++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/specifyweb/frontend/js_src/lib/components/QueryBuilder/__tests__/fromTree.test.ts b/specifyweb/frontend/js_src/lib/components/QueryBuilder/__tests__/fromTree.test.ts index 4f96cf30e7d..dd30ccb67c4 100644 --- a/specifyweb/frontend/js_src/lib/components/QueryBuilder/__tests__/fromTree.test.ts +++ b/specifyweb/frontend/js_src/lib/components/QueryBuilder/__tests__/fromTree.test.ts @@ -21,13 +21,17 @@ const fullNames = { GeologicTimePeriod: 'Paleocene', LithoStrat: 'Cretaceous', }; -allTrees.map((table, rankId) => { + +// TODO: Add static data for Tectonic Unit and replace testingTrees with allTrees +const testingTrees = allTrees.filter((t) => t !== 'TectonicUnit'); +testingTrees.map((table, rankId) => { const nodeId = rankId * 4; const rankUrl = getResourceApiUrl(`${table}TreeDefItem`, rankId); overrideAjax(getResourceApiUrl(table, nodeId), () => addMissingFields(table, { id: nodeId, definitionItem: rankUrl, + // @ts-expect-error Remove error suppress after adding static data for TectonicUnit fullName: fullNames[table], resource_uri: getResourceApiUrl(table, nodeId), }) @@ -35,6 +39,7 @@ allTrees.map((table, rankId) => { overrideAjax(rankUrl, () => addMissingFields(`${table}TreeDefItem`, { id: rankId, + // @ts-expect-error Remove error suppress after adding static data for TectonicUnit name: rankNames[table], resource_uri: rankUrl, }) @@ -44,6 +49,7 @@ allTrees.map((table, rankId) => { test('queryFromTree', async () => expect( Promise.all( - allTrees.map(async (tree, index) => queryFromTree(tree, index * 4)) + // TODO: Add static data for Tectonic Unit and replace testingTrees with allTrees + testingTrees.map(async (tree, index) => queryFromTree(tree, index * 4)) ) ).resolves.toMatchSnapshot()); diff --git a/specifyweb/frontend/js_src/lib/components/QueryBuilder/fromTree.ts b/specifyweb/frontend/js_src/lib/components/QueryBuilder/fromTree.ts index e87c6a5353c..5b04dea474b 100644 --- a/specifyweb/frontend/js_src/lib/components/QueryBuilder/fromTree.ts +++ b/specifyweb/frontend/js_src/lib/components/QueryBuilder/fromTree.ts @@ -153,7 +153,15 @@ const defaultFields: RR< ]; }, TectonicUnit: async (_nodeId, _rankName) => { - return [makeField('catalogNumber', {})]; + // TODO: Fields below are a placeholder. Remove once we determine the requirements for querying Tectonic trees + return [ + makeField('catalogNumber', {}), + makeField('determinations.taxon.fullName', {}), + makeField('determinations.isCurrent', { + isDisplay: false, + operStart: queryFieldFilters.trueOrNull.id, + }), + ]; }, }; From 0f8e085554921239911c773ae6ad0350075eab6f Mon Sep 17 00:00:00 2001 From: Sharad S Date: Wed, 9 Oct 2024 11:08:36 -0400 Subject: [PATCH 12/34] Fix frontend tests --- .vscode/settings.json | 3 + .../__snapshots__/treeRanks.test.ts.snap | 387 ++++++++++++++++++ .../__tests__/treeRanks.test.ts | 4 +- .../QueryBuilder/__tests__/fromTree.test.ts | 4 +- 4 files changed, 394 insertions(+), 4 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000000..3377bead04c --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "typescript.tsdk": "./specifyweb/frontend/js_src/node_modules/typescript/lib/" +} \ No newline at end of file diff --git a/specifyweb/frontend/js_src/lib/components/InitialContext/__tests__/__snapshots__/treeRanks.test.ts.snap b/specifyweb/frontend/js_src/lib/components/InitialContext/__tests__/__snapshots__/treeRanks.test.ts.snap index def76761ae1..ecb50e8a773 100644 --- a/specifyweb/frontend/js_src/lib/components/InitialContext/__tests__/__snapshots__/treeRanks.test.ts.snap +++ b/specifyweb/frontend/js_src/lib/components/InitialContext/__tests__/__snapshots__/treeRanks.test.ts.snap @@ -372,3 +372,390 @@ exports[`getDisciplineTrees 1`] = ` "Taxon", ] `; + +exports[`queryFromTree 1`] = ` +[ + { + "_tableName": "SpQuery", + "contextname": "CollectionObject", + "contexttableid": 1, + "countonly": false, + "fields": [ + { + "_tableName": "SpQueryField", + "fieldname": "catalogNumber", + "isdisplay": true, + "isnot": false, + "isrelfld": false, + "operstart": 8, + "sorttype": 0, + "startvalue": "", + "stringid": "1.collectionobject.catalogNumber", + "tablelist": "1", + }, + { + "_tableName": "SpQueryField", + "fieldname": "fullName", + "isdisplay": true, + "isnot": false, + "isrelfld": false, + "operstart": 8, + "sorttype": 1, + "startvalue": "", + "stringid": "1,9-determinations,4.taxon.fullName", + "tablelist": "1,9-determinations,4", + }, + { + "_tableName": "SpQueryField", + "fieldname": "isCurrent", + "isdisplay": false, + "isnot": false, + "isrelfld": false, + "operstart": 13, + "sorttype": 0, + "startvalue": "", + "stringid": "1,9-determinations.determination.isCurrent", + "tablelist": "1,9-determinations", + }, + { + "_tableName": "SpQueryField", + "fieldname": "localityName", + "isdisplay": true, + "isnot": false, + "isrelfld": false, + "operstart": 8, + "sorttype": 0, + "startvalue": "", + "stringid": "1,10,2.locality.localityName", + "tablelist": "1,10,2", + }, + { + "_tableName": "SpQueryField", + "fieldname": "fullName", + "isdisplay": true, + "isnot": false, + "isrelfld": false, + "operstart": 8, + "sorttype": 1, + "startvalue": "", + "stringid": "1,10,2,3.geography.fullName", + "tablelist": "1,10,2,3", + }, + { + "_tableName": "SpQueryField", + "fieldname": "County ID", + "isdisplay": false, + "isnot": false, + "isrelfld": false, + "operstart": 1, + "sorttype": 0, + "startvalue": "0", + "stringid": "1,10,2,3.geography.County ID", + "tablelist": "1,10,2,3", + }, + ], + "formatauditrecids": false, + "isfavorite": true, + "name": "Collection Object using \\"Los Angeles County\\"", + "ordinal": 32767, + "selectdistinct": false, + "specifyuser": "/api/specify/specifyuser/2/", + }, + { + "_tableName": "SpQuery", + "contextname": "CollectionObject", + "contexttableid": 1, + "countonly": false, + "fields": [ + { + "_tableName": "SpQueryField", + "fieldname": "catalogNumber", + "isdisplay": true, + "isnot": false, + "isrelfld": false, + "operstart": 8, + "sorttype": 1, + "startvalue": "", + "stringid": "1.collectionobject.catalogNumber", + "tablelist": "1", + }, + { + "_tableName": "SpQueryField", + "fieldname": "fullName", + "isdisplay": true, + "isnot": false, + "isrelfld": false, + "operstart": 8, + "sorttype": 0, + "startvalue": "", + "stringid": "1,9-determinations,4.taxon.fullName", + "tablelist": "1,9-determinations,4", + }, + { + "_tableName": "SpQueryField", + "fieldname": "isCurrent", + "isdisplay": false, + "isnot": false, + "isrelfld": false, + "operstart": 13, + "sorttype": 0, + "startvalue": "", + "stringid": "1,9-determinations.determination.isCurrent", + "tablelist": "1,9-determinations", + }, + { + "_tableName": "SpQueryField", + "fieldname": "fullName", + "isdisplay": true, + "isnot": false, + "isrelfld": false, + "operstart": 8, + "sorttype": 0, + "startvalue": "", + "stringid": "1,63-preparations,58.storage.fullName", + "tablelist": "1,63-preparations,58", + }, + { + "_tableName": "SpQueryField", + "fieldname": "Cabinet ID", + "isdisplay": false, + "isnot": false, + "isrelfld": false, + "operstart": 1, + "sorttype": 0, + "startvalue": "4", + "stringid": "1,63-preparations,58.storage.Cabinet ID", + "tablelist": "1,63-preparations,58", + }, + ], + "formatauditrecids": false, + "isfavorite": true, + "name": "Collection Object using \\"Cabinet 1\\"", + "ordinal": 32767, + "selectdistinct": false, + "specifyuser": "/api/specify/specifyuser/2/", + }, + { + "_tableName": "SpQuery", + "contextname": "CollectionObject", + "contexttableid": 1, + "countonly": false, + "fields": [ + { + "_tableName": "SpQueryField", + "fieldname": "catalogNumber", + "isdisplay": true, + "isnot": false, + "isrelfld": false, + "operstart": 8, + "sorttype": 1, + "startvalue": "", + "stringid": "1.collectionobject.catalogNumber", + "tablelist": "1", + }, + { + "_tableName": "SpQueryField", + "fieldname": "fullName", + "isdisplay": true, + "isnot": false, + "isrelfld": false, + "operstart": 8, + "sorttype": 0, + "startvalue": "", + "stringid": "1,9-determinations,4.taxon.fullName", + "tablelist": "1,9-determinations,4", + }, + { + "_tableName": "SpQueryField", + "fieldname": "Species ID", + "isdisplay": false, + "isnot": false, + "isrelfld": false, + "operstart": 1, + "sorttype": 0, + "startvalue": "8", + "stringid": "1,9-determinations,4.taxon.Species ID", + "tablelist": "1,9-determinations,4", + }, + { + "_tableName": "SpQueryField", + "fieldname": "isCurrent", + "isdisplay": false, + "isnot": false, + "isrelfld": false, + "operstart": 13, + "sorttype": 0, + "startvalue": "", + "stringid": "1,9-determinations.determination.isCurrent", + "tablelist": "1,9-determinations", + }, + { + "_tableName": "SpQueryField", + "fieldname": "localityName", + "isdisplay": true, + "isnot": false, + "isrelfld": false, + "operstart": 8, + "sorttype": 0, + "startvalue": "", + "stringid": "1,10,2.locality.localityName", + "tablelist": "1,10,2", + }, + ], + "formatauditrecids": false, + "isfavorite": true, + "name": "Collection Object using \\"Carpiodes velifer\\"", + "ordinal": 32767, + "selectdistinct": false, + "specifyuser": "/api/specify/specifyuser/2/", + }, + { + "_tableName": "SpQuery", + "contextname": "CollectionObject", + "contexttableid": 1, + "countonly": false, + "fields": [ + { + "_tableName": "SpQueryField", + "fieldname": "catalogNumber", + "isdisplay": true, + "isnot": false, + "isrelfld": false, + "operstart": 8, + "sorttype": 0, + "startvalue": "", + "stringid": "1.collectionobject.catalogNumber", + "tablelist": "1", + }, + { + "_tableName": "SpQueryField", + "fieldname": "fullName", + "isdisplay": true, + "isnot": false, + "isrelfld": false, + "operstart": 8, + "sorttype": 1, + "startvalue": "", + "stringid": "1,9-determinations,4.taxon.fullName", + "tablelist": "1,9-determinations,4", + }, + { + "_tableName": "SpQueryField", + "fieldname": "isCurrent", + "isdisplay": false, + "isnot": false, + "isrelfld": false, + "operstart": 13, + "sorttype": 0, + "startvalue": "", + "stringid": "1,9-determinations.determination.isCurrent", + "tablelist": "1,9-determinations", + }, + { + "_tableName": "SpQueryField", + "fieldname": "fullName", + "isdisplay": true, + "isnot": false, + "isrelfld": false, + "operstart": 8, + "sorttype": 0, + "startvalue": "", + "stringid": "1,32,46-chronosStrat.geologictimeperiod.fullName", + "tablelist": "1,32,46-chronosStrat", + }, + { + "_tableName": "SpQueryField", + "fieldname": "Epoch ID", + "isdisplay": false, + "isnot": false, + "isrelfld": false, + "operstart": 1, + "sorttype": 0, + "startvalue": "12", + "stringid": "1,32,46-chronosStrat.geologictimeperiod.Epoch ID", + "tablelist": "1,32,46-chronosStrat", + }, + ], + "formatauditrecids": false, + "isfavorite": true, + "name": "Collection Object using \\"Paleocene\\"", + "ordinal": 32767, + "selectdistinct": false, + "specifyuser": "/api/specify/specifyuser/2/", + }, + { + "_tableName": "SpQuery", + "contextname": "CollectionObject", + "contexttableid": 1, + "countonly": false, + "fields": [ + { + "_tableName": "SpQueryField", + "fieldname": "catalogNumber", + "isdisplay": true, + "isnot": false, + "isrelfld": false, + "operstart": 8, + "sorttype": 0, + "startvalue": "", + "stringid": "1.collectionobject.catalogNumber", + "tablelist": "1", + }, + { + "_tableName": "SpQueryField", + "fieldname": "fullName", + "isdisplay": true, + "isnot": false, + "isrelfld": false, + "operstart": 8, + "sorttype": 1, + "startvalue": "", + "stringid": "1,9-determinations,4.taxon.fullName", + "tablelist": "1,9-determinations,4", + }, + { + "_tableName": "SpQueryField", + "fieldname": "isCurrent", + "isdisplay": false, + "isnot": false, + "isrelfld": false, + "operstart": 13, + "sorttype": 0, + "startvalue": "", + "stringid": "1,9-determinations.determination.isCurrent", + "tablelist": "1,9-determinations", + }, + { + "_tableName": "SpQueryField", + "fieldname": "fullName", + "isdisplay": true, + "isnot": false, + "isrelfld": false, + "operstart": 8, + "sorttype": 0, + "startvalue": "", + "stringid": "1,32,100.lithostrat.fullName", + "tablelist": "1,32,100", + }, + { + "_tableName": "SpQueryField", + "fieldname": "Formation ID", + "isdisplay": false, + "isnot": false, + "isrelfld": false, + "operstart": 1, + "sorttype": 0, + "startvalue": "16", + "stringid": "1,32,100.lithostrat.Formation ID", + "tablelist": "1,32,100", + }, + ], + "formatauditrecids": false, + "isfavorite": true, + "name": "Collection Object using \\"Cretaceous\\"", + "ordinal": 32767, + "selectdistinct": false, + "specifyuser": "/api/specify/specifyuser/2/", + }, +] +`; diff --git a/specifyweb/frontend/js_src/lib/components/InitialContext/__tests__/treeRanks.test.ts b/specifyweb/frontend/js_src/lib/components/InitialContext/__tests__/treeRanks.test.ts index 3d67c24c04c..441c26c460c 100644 --- a/specifyweb/frontend/js_src/lib/components/InitialContext/__tests__/treeRanks.test.ts +++ b/specifyweb/frontend/js_src/lib/components/InitialContext/__tests__/treeRanks.test.ts @@ -1,8 +1,8 @@ import { requireContext } from '../../../tests/helpers'; import { theories } from '../../../tests/utils'; import { tables } from '../../DataModel/tables'; +import { testingTrees } from '../../QueryBuilder/__tests__/fromTree.test'; import { - allTrees, exportsForTests, getDisciplineTrees, getTreeDefinitionItems, @@ -66,7 +66,7 @@ describe('strictGetTreeDefinitionItems', () => { }); test('getTreeScope', () => - expect(Object.fromEntries(allTrees.map((tree) => [tree, getTreeScope(tree)]))) + expect(Object.fromEntries(testingTrees.map((tree) => [tree, getTreeScope(tree)]))) .toMatchInlineSnapshot(` { "Geography": "discipline", diff --git a/specifyweb/frontend/js_src/lib/components/QueryBuilder/__tests__/fromTree.test.ts b/specifyweb/frontend/js_src/lib/components/QueryBuilder/__tests__/fromTree.test.ts index dd30ccb67c4..5728643ee5d 100644 --- a/specifyweb/frontend/js_src/lib/components/QueryBuilder/__tests__/fromTree.test.ts +++ b/specifyweb/frontend/js_src/lib/components/QueryBuilder/__tests__/fromTree.test.ts @@ -22,8 +22,8 @@ const fullNames = { LithoStrat: 'Cretaceous', }; -// TODO: Add static data for Tectonic Unit and replace testingTrees with allTrees -const testingTrees = allTrees.filter((t) => t !== 'TectonicUnit'); +// TODO: Add static data for Tectonic Unit and replace ALL testingTrees usages with allTrees +export const testingTrees = allTrees.filter((t) => t !== 'TectonicUnit'); testingTrees.map((table, rankId) => { const nodeId = rankId * 4; const rankUrl = getResourceApiUrl(`${table}TreeDefItem`, rankId); From 7cfbb9970f0d04af8702f8f8ca0552222cafa6ce Mon Sep 17 00:00:00 2001 From: Sharad S Date: Wed, 9 Oct 2024 11:09:03 -0400 Subject: [PATCH 13/34] Add tectonic tree permission urls --- specifyweb/specify/tree_views.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/specifyweb/specify/tree_views.py b/specifyweb/specify/tree_views.py index 41eca5b1c82..0fddcf38b7e 100644 --- a/specifyweb/specify/tree_views.py +++ b/specifyweb/specify/tree_views.py @@ -29,7 +29,7 @@ 'Geography', 'Geologictimeperiod', 'Lithostrat'] COMMON_TREES: Tuple[TREE_TABLE, ...] = ['Taxon', 'Storage', - 'Geography'] + 'Geography', 'Tectonicunit'] ALL_TRESS: Tuple[TREE_TABLE, ...] = [ *COMMON_TREES, 'Geologictimeperiod', 'Lithostrat'] @@ -522,6 +522,15 @@ class LithostratMutationPT(PermissionTarget): repair = PermissionTargetAction() +class TectonicUnitMutationPT(PermissionTarget): + resource = "/tree/edit/tectonicunit" + merge = PermissionTargetAction() + move = PermissionTargetAction() + synonymize = PermissionTargetAction() + desynonymize = PermissionTargetAction() + repair = PermissionTargetAction() + + def perm_target(tree): return { 'taxon': TaxonMutationPT, @@ -529,4 +538,5 @@ def perm_target(tree): 'storage': StorageMutationPT, 'geologictimeperiod': GeologictimeperiodMutationPT, 'lithostrat': LithostratMutationPT, + 'tectonicunit': TectonicUnitMutationPT }[tree] From 22d1f069f7a0e641c9cc7e5d26ca376385c00414 Mon Sep 17 00:00:00 2001 From: Sharad S Date: Wed, 9 Oct 2024 11:09:20 -0400 Subject: [PATCH 14/34] Fix tectonic tree and age models --- .../js_src/lib/components/DataModel/types.ts | 3 +- specifyweb/specify/datamodel.py | 4 +- .../0005_fix_tectonic_tree_fields.py | 111 ++++++++++++++++++ specifyweb/specify/models.py | 23 ++-- 4 files changed, 131 insertions(+), 10 deletions(-) create mode 100644 specifyweb/specify/migrations/0005_fix_tectonic_tree_fields.py diff --git a/specifyweb/frontend/js_src/lib/components/DataModel/types.ts b/specifyweb/frontend/js_src/lib/components/DataModel/types.ts index 327626f5963..ba3cce03247 100644 --- a/specifyweb/frontend/js_src/lib/components/DataModel/types.ts +++ b/specifyweb/frontend/js_src/lib/components/DataModel/types.ts @@ -2585,6 +2585,7 @@ export type Discipline = { readonly lithoStratTreeDef: LithoStratTreeDef | null; readonly modifiedByAgent: Agent | null; readonly taxonTreeDef: TaxonTreeDef | null; + readonly tectonicUnitTreeDef: TectonicUnitTreeDef | null; }; readonly toManyDependent: RR; readonly toManyIndependent: { @@ -6804,5 +6805,5 @@ export type TectonicUnit = { readonly parent: TectonicUnit; }; readonly toManyDependent: RR; - readonly toManyIndependent: RR; + readonly toManyIndependent: { readonly acceptedChildren: RA }; }; diff --git a/specifyweb/specify/datamodel.py b/specifyweb/specify/datamodel.py index 2156126355a..1f07596312b 100644 --- a/specifyweb/specify/datamodel.py +++ b/specifyweb/specify/datamodel.py @@ -2992,6 +2992,7 @@ Relationship(name='taxonTreeDef', type='many-to-one',required=False, relatedModelName='TaxonTreeDef', column='TaxonTreeDefID', otherSideName='disciplines'), Relationship(name='geologicTimePeriodTreeDef', type='many-to-one',required=True, relatedModelName='GeologicTimePeriodTreeDef', column='GeologicTimePeriodTreeDefID', otherSideName='disciplines'), Relationship(name='lithoStratTreeDef', type='many-to-one',required=False, relatedModelName='LithoStratTreeDef', column='LithoStratTreeDefID', otherSideName='disciplines'), + Relationship(name='tectonicUnitTreeDef', type='many-to-one',required=False, relatedModelName='TectonicUnitTreeDef', column='TectonicUnitTreeDefID', otherSideName='disciplines'), Relationship(name='modifiedByAgent', type='many-to-one',required=False, relatedModelName='Agent', column='ModifiedByAgentID'), Relationship(name='numberingSchemes', type='many-to-many',required=False, relatedModelName='AutoNumberingScheme', otherSideName='disciplines'), Relationship(name='spExportSchemas', type='one-to-many',required=False, relatedModelName='SpExportSchema', otherSideName='discipline'), @@ -8686,7 +8687,8 @@ ], relationships=[ - Relationship(name='acceptedTectonicUnit', type='many-to-one', required=False, relatedModelName='TectonicUnit', column='AcceptedID'), + Relationship(name='acceptedChildren', type='one-to-many',required=False, relatedModelName='TectonicUnit', otherSideName='acceptedTectonicUnit'), + Relationship(name='acceptedTectonicUnit', type='many-to-one', required=False, relatedModelName='TectonicUnit', column='AcceptedID', otherSideName='acceptedChildren'), Relationship(name='createdByAgent', type='many-to-one', required=False, relatedModelName='Agent', column='CreatedByAgentID'), Relationship(name='modifiedByAgent', type='many-to-one', required=False, relatedModelName='Agent', column='ModifiedByAgentID'), Relationship(name='parent', type='many-to-one', required=True, relatedModelName='TectonicUnit', column='ParentID'), diff --git a/specifyweb/specify/migrations/0005_fix_tectonic_tree_fields.py b/specifyweb/specify/migrations/0005_fix_tectonic_tree_fields.py new file mode 100644 index 00000000000..54db22db541 --- /dev/null +++ b/specifyweb/specify/migrations/0005_fix_tectonic_tree_fields.py @@ -0,0 +1,111 @@ +# Generated by Django 3.2.15 on 2024-10-09 14:58 + +from django.db import migrations, models +import specifyweb.specify.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('specify', '0004_stratigraphy_age'), + ] + + operations = [ + migrations.RenameField( + model_name='tectonicunittreedefitem', + old_name='parentitem', + new_name='parent', + ), + migrations.RemoveField( + model_name='tectonicunit', + name='tectonicunittreedef', + ), + migrations.RemoveField( + model_name='tectonicunit', + name='tectonicunittreedefitem', + ), + migrations.RemoveField( + model_name='tectonicunittreedefitem', + name='tectonicunittreedef', + ), + migrations.AddField( + model_name='tectonicunit', + name='definition', + field=models.ForeignKey(db_column='TectonicUnitTreeDefID', null=True, on_delete=specifyweb.specify.models.protect_with_blockers, related_name='treeentries', to='specify.tectonicunittreedef'), + ), + migrations.AddField( + model_name='tectonicunit', + name='definitionitem', + field=models.ForeignKey(db_column='TectonicUnitTreeDefItemID', null=True, on_delete=specifyweb.specify.models.protect_with_blockers, related_name='treeentries', to='specify.tectonicunittreedefitem'), + ), + migrations.AddField( + model_name='tectonicunittreedefitem', + name='treedef', + field=models.ForeignKey(db_column='TectonicUnitTreeDefID', null=True, on_delete=specifyweb.specify.models.protect_with_blockers, related_name='treedefitems', to='specify.tectonicunittreedef'), + ), + migrations.AlterField( + model_name='absoluteageattachment', + name='id', + field=models.AutoField(db_column='absoluteageattachmentid', primary_key=True, serialize=False), + ), + migrations.AlterField( + model_name='absoluteagecitation', + name='id', + field=models.AutoField(db_column='absoluteagecitationid', primary_key=True, serialize=False), + ), + migrations.AlterField( + model_name='relativeage', + name='agename', + field=models.ForeignKey(db_column='AgeNameID', null=True, on_delete=specifyweb.specify.models.protect_with_blockers, related_name='agename', to='specify.geologictimeperiod'), + ), + migrations.AlterField( + model_name='relativeage', + name='agenameend', + field=models.ForeignKey(db_column='AgeNameEndID', null=True, on_delete=specifyweb.specify.models.protect_with_blockers, related_name='agenameend', to='specify.geologictimeperiod'), + ), + migrations.AlterField( + model_name='relativeage', + name='id', + field=models.AutoField(db_column='relativeageid', primary_key=True, serialize=False), + ), + migrations.AlterField( + model_name='relativeageattachment', + name='id', + field=models.AutoField(db_column='relativeageattachmentid', primary_key=True, serialize=False), + ), + migrations.AlterField( + model_name='relativeagecitation', + name='id', + field=models.AutoField(db_column='relativeagecitationid', primary_key=True, serialize=False), + ), + migrations.AlterField( + model_name='tectonicunit', + name='acceptedtectonicunit', + field=models.ForeignKey(db_column='AcceptedID', null=True, on_delete=specifyweb.specify.models.protect_with_blockers, related_name='acceptedchildren', to='specify.tectonicunit'), + ), + migrations.AlterField( + model_name='tectonicunit', + name='id', + field=models.AutoField(db_column='tectonicunitid', primary_key=True, serialize=False), + ), + migrations.AlterField( + model_name='tectonicunittreedef', + name='discipline', + field=models.ForeignKey(db_column='DisciplineID', null=True, on_delete=specifyweb.specify.models.protect_with_blockers, related_name='tectonicunittreedefs', to='specify.discipline'), + ), + migrations.AlterField( + model_name='tectonicunittreedef', + name='fullnamedirection', + field=models.IntegerField(blank=True, db_column='FullNameDirection', default=0, null=True), + ), + migrations.AlterField( + model_name='tectonicunittreedef', + name='id', + field=models.AutoField(db_column='tectonicunittreedefid', primary_key=True, serialize=False), + ), + migrations.AlterField( + model_name='tectonicunittreedefitem', + name='id', + field=models.AutoField(db_column='tectonicunittreedefitemid', primary_key=True, serialize=False), + ), + ] diff --git a/specifyweb/specify/models.py b/specifyweb/specify/models.py index 29e4630870d..25d69f46966 100644 --- a/specifyweb/specify/models.py +++ b/specifyweb/specify/models.py @@ -2825,6 +2825,7 @@ class Discipline(model_extras.Discipline): taxontreedef = models.ForeignKey('TaxonTreeDef', db_column='TaxonTreeDefID', related_name='disciplines', null=True, on_delete=protect_with_blockers) geologictimeperiodtreedef = models.ForeignKey('GeologicTimePeriodTreeDef', db_column='GeologicTimePeriodTreeDefID', related_name='disciplines', null=False, on_delete=protect_with_blockers) lithostrattreedef = models.ForeignKey('LithoStratTreeDef', db_column='LithoStratTreeDefID', related_name='disciplines', null=True, on_delete=protect_with_blockers) + tectonicunittreedef = models.ForeignKey('TectonicUnitTreeDef', db_column='TectonicUnitTreeDefID', related_name='disciplines', null=True, on_delete=protect_with_blockers) modifiedbyagent = models.ForeignKey('Agent', db_column='ModifiedByAgentID', related_name='+', null=True, on_delete=protect_with_blockers) class Meta: @@ -7619,7 +7620,7 @@ class AbsoluteAge(models.Model): specify_model = datamodel.get_table('absoluteage') # ID Field - id = models.AutoField(primary_key=True, db_column='absoluteageid') + id = models.AutoField(db_column='AbsoluteAgeID', primary_key=True, serialize=False) # Fields absoluteage = models.DecimalField(blank=True, max_digits=22, decimal_places=10, null=True, unique=False, db_column='AbsoluteAge', db_index=False) @@ -7643,6 +7644,9 @@ class AbsoluteAge(models.Model): # Relationships: Many-to-One agent1 = models.ForeignKey('Agent', db_column='Agent1ID', related_name='+', null=True, on_delete=protect_with_blockers) collectionobject = models.ForeignKey('CollectionObject', db_column='CollectionObjectID', related_name='absoluteages', null=False, on_delete=models.CASCADE) + absoluteageattachment = models.ForeignKey(db_column='AbsoluteAgeAttachmentID', null=True, on_delete=protect_with_blockers, related_name='absoluteages', to='specify.absoluteageattachment') + createdbyagent = models.ForeignKey(db_column='CreatedByAgentID', null=True, on_delete=protect_with_blockers, related_name='+', to='specify.agent') + modifiedbyagent = models.ForeignKey(db_column='ModifiedByAgentID', null=True, on_delete=protect_with_blockers, related_name='+', to='specify.agent') class Meta: db_table = 'absoluteage' @@ -7683,6 +7687,9 @@ class RelativeAge(models.Model): agent1 = models.ForeignKey('Agent', db_column='Agent1ID', related_name='+', null=True, on_delete=protect_with_blockers) agent2 = models.ForeignKey('Agent', db_column='Agent2ID', related_name='+', null=True, on_delete=protect_with_blockers) collectionobject = models.ForeignKey('CollectionObject', db_column='CollectionObjectID', related_name='relativeages', null=False, on_delete=models.CASCADE) + relativeageattachment = models.ForeignKey(db_column='RelativeAgeAttachmentID', null=True, on_delete=protect_with_blockers, related_name='relativeages', to='specify.relativeageattachment') + createdbyagent = models.ForeignKey(db_column='CreatedByAgentID', null=True, on_delete=protect_with_blockers, related_name='+', to='specify.agent') + modifiedbyagent = models.ForeignKey(db_column='ModifiedByAgentID', null=True, on_delete=protect_with_blockers, related_name='+', to='specify.agent') class Meta: db_table = 'relativeage' @@ -7800,7 +7807,7 @@ class Meta: save = partialmethod(custom_save) -class TectonicUnitTreeDef(models.Model): +class Tectonicunittreedef(models.Model): specify_model = datamodel.get_table('tectonicunittreedef') # ID Field @@ -7825,7 +7832,7 @@ class Meta: save = partialmethod(custom_save) -class TectonicUnitTreeDefItem(models.Model): +class Tectonicunittreedefitem(models.Model): specify_model = datamodel.get_table('tectonicUnittreedefitem') # ID Field @@ -7848,8 +7855,8 @@ class TectonicUnitTreeDefItem(models.Model): # Relationships: Many-to-One createdbyagent = models.ForeignKey('Agent', db_column='CreatedByAgentID', related_name='+', null=True, on_delete=protect_with_blockers) modifiedbyagent = models.ForeignKey('Agent', db_column='ModifiedByAgentID', related_name='+', null=True, on_delete=protect_with_blockers) - parentitem = models.ForeignKey('TectonicUnitTreeDefItem', db_column='ParentItemID', related_name='children', null=True, on_delete=protect_with_blockers) - tectonicunittreedef = models.ForeignKey('TectonicUnitTreeDef', db_column='TectonicUnitTreeDefID', related_name='tectonicunittreedefitems', null=False, on_delete=protect_with_blockers) + parent = models.ForeignKey('TectonicUnitTreeDefItem', db_column='ParentItemID', related_name='children', null=True, on_delete=protect_with_blockers) + treedef = models.ForeignKey('TectonicUnitTreeDef', db_column='TectonicUnitTreeDefID', related_name='treedefitems', null=True, on_delete=protect_with_blockers) class Meta: db_table = 'tectonicunittreedefitem' @@ -7857,7 +7864,7 @@ class Meta: save = partialmethod(custom_save) -class TectonicUnit(models.Model): +class Tectonicunit(models.Model): specify_model = datamodel.get_table('tectonicunit') # ID Field @@ -7884,9 +7891,9 @@ class TectonicUnit(models.Model): # Relationships: Many-to-One acceptedtectonicunit = models.ForeignKey('TectonicUnit', db_column='AcceptedID', related_name='acceptedchildren', null=True, on_delete=protect_with_blockers) - tectonicunittreedefitem = models.ForeignKey('TectonicUnitTreeDefItem', db_column='TectonicUnitTreeDefItemID', related_name='tectonicunits', null=False, on_delete=protect_with_blockers) + definitionitem = models.ForeignKey('TectonicUnitTreeDefItem', db_column='TectonicUnitTreeDefItemID', related_name='treeentries', null=True, on_delete=protect_with_blockers) parent = models.ForeignKey('TectonicUnit', db_column='ParentID', related_name='children', null=True, on_delete=protect_with_blockers) - tectonicunittreedef = models.ForeignKey('TectonicUnitTreeDef', db_column='TectonicUnitTreeDefID', related_name='tectonicunits', null=False, on_delete=protect_with_blockers) + definition = models.ForeignKey('TectonicUnitTreeDef', db_column='TectonicUnitTreeDefID', related_name='treeentries', null=True, on_delete=protect_with_blockers) createdbyagent = models.ForeignKey('Agent', db_column='CreatedByAgentID', related_name='+', null=True, on_delete=protect_with_blockers) modifiedbyagent = models.ForeignKey('Agent', db_column='ModifiedByAgentID', related_name='+', null=True, on_delete=protect_with_blockers) From 9b478d98e6556aa81a7693c6b8df6ba46301f5c9 Mon Sep 17 00:00:00 2001 From: Sharad S Date: Wed, 9 Oct 2024 15:13:08 +0000 Subject: [PATCH 15/34] Lint code with ESLint and Prettier Triggered by 22d1f069f7a0e641c9cc7e5d26ca376385c00414 on branch refs/heads/issue-5317 --- .../components/InitialContext/__tests__/treeRanks.test.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/specifyweb/frontend/js_src/lib/components/InitialContext/__tests__/treeRanks.test.ts b/specifyweb/frontend/js_src/lib/components/InitialContext/__tests__/treeRanks.test.ts index 441c26c460c..0d778d27776 100644 --- a/specifyweb/frontend/js_src/lib/components/InitialContext/__tests__/treeRanks.test.ts +++ b/specifyweb/frontend/js_src/lib/components/InitialContext/__tests__/treeRanks.test.ts @@ -66,8 +66,9 @@ describe('strictGetTreeDefinitionItems', () => { }); test('getTreeScope', () => - expect(Object.fromEntries(testingTrees.map((tree) => [tree, getTreeScope(tree)]))) - .toMatchInlineSnapshot(` + expect( + Object.fromEntries(testingTrees.map((tree) => [tree, getTreeScope(tree)])) + ).toMatchInlineSnapshot(` { "Geography": "discipline", "GeologicTimePeriod": "discipline", From 4c613aaa7018b425642b65c32ad5a201a4b5e35d Mon Sep 17 00:00:00 2001 From: Sharad S <16229739+sharadsw@users.noreply.github.com> Date: Wed, 9 Oct 2024 11:31:36 -0400 Subject: [PATCH 16/34] Delete .vscode/settings.json --- .vscode/settings.json | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 3377bead04c..00000000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "typescript.tsdk": "./specifyweb/frontend/js_src/node_modules/typescript/lib/" -} \ No newline at end of file From 1e99c45a6f21ad00ef924cc793c893ff5b1b17fb Mon Sep 17 00:00:00 2001 From: Sharad S Date: Wed, 9 Oct 2024 11:57:42 -0400 Subject: [PATCH 17/34] Reorder migrations --- .../js_src/lib/components/DataModel/types.ts | 10 +++++----- .../0005_collectionobjectgroup_parentcojo.py | 19 +++++++++++++++++++ ...ds.py => 0006_fix_tectonic_tree_fields.py} | 2 +- 3 files changed, 25 insertions(+), 6 deletions(-) create mode 100644 specifyweb/specify/migrations/0005_collectionobjectgroup_parentcojo.py rename specifyweb/specify/migrations/{0005_fix_tectonic_tree_fields.py => 0006_fix_tectonic_tree_fields.py} (98%) diff --git a/specifyweb/frontend/js_src/lib/components/DataModel/types.ts b/specifyweb/frontend/js_src/lib/components/DataModel/types.ts index ba3cce03247..7c2f0385006 100644 --- a/specifyweb/frontend/js_src/lib/components/DataModel/types.ts +++ b/specifyweb/frontend/js_src/lib/components/DataModel/types.ts @@ -6516,11 +6516,9 @@ export type CollectionObjectGroup = { readonly collection: Collection | null; readonly createdByAgent: Agent | null; readonly modifiedByAgent: Agent | null; + readonly parentCojo: CollectionObjectGroupJoin | null; }; - readonly toManyDependent: { - readonly cojo: RA; - readonly parentCojos: RA; - }; + readonly toManyDependent: { readonly cojo: RA }; readonly toManyIndependent: RR; }; export type CollectionObjectGroupJoin = { @@ -6549,7 +6547,9 @@ export type CollectionObjectGroupJoin = { readonly parentCog: CollectionObjectGroup; }; readonly toManyDependent: RR; - readonly toManyIndependent: RR; + readonly toManyIndependent: { + readonly collectionobjectgroup: RA; + }; }; export type CollectionObjectGroupType = { readonly tableName: 'CollectionObjectGroupType'; diff --git a/specifyweb/specify/migrations/0005_collectionobjectgroup_parentcojo.py b/specifyweb/specify/migrations/0005_collectionobjectgroup_parentcojo.py new file mode 100644 index 00000000000..dc116f75a48 --- /dev/null +++ b/specifyweb/specify/migrations/0005_collectionobjectgroup_parentcojo.py @@ -0,0 +1,19 @@ +# Generated by Django 3.2.15 on 2024-10-07 19:18 + +from django.db import migrations, models +import specifyweb.specify.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('specify', '0004_stratigraphy_age'), + ] + + operations = [ + migrations.AddField( + model_name='collectionobjectgroup', + name='parentcojo', + field=models.ForeignKey(db_column='CollectionObjectGroupJoinID', null=True, on_delete=specifyweb.specify.models.protect_with_blockers, related_name='collectionobjectgroup', to='specify.collectionobjectgroupjoin'), + ), + ] diff --git a/specifyweb/specify/migrations/0005_fix_tectonic_tree_fields.py b/specifyweb/specify/migrations/0006_fix_tectonic_tree_fields.py similarity index 98% rename from specifyweb/specify/migrations/0005_fix_tectonic_tree_fields.py rename to specifyweb/specify/migrations/0006_fix_tectonic_tree_fields.py index 54db22db541..40ceee06aa2 100644 --- a/specifyweb/specify/migrations/0005_fix_tectonic_tree_fields.py +++ b/specifyweb/specify/migrations/0006_fix_tectonic_tree_fields.py @@ -7,7 +7,7 @@ class Migration(migrations.Migration): dependencies = [ - ('specify', '0004_stratigraphy_age'), + ('specify', '0005_collectionobjectgroup_parentcojo'), ] operations = [ From 907c3e7a432cd1635ebf76143d90b1bc36fd04fc Mon Sep 17 00:00:00 2001 From: Sharad S Date: Wed, 9 Oct 2024 12:37:52 -0400 Subject: [PATCH 18/34] Remove and add tectonic tree to discipline --- .../migrations/0006_fix_tectonic_tree_fields.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/specifyweb/specify/migrations/0006_fix_tectonic_tree_fields.py b/specifyweb/specify/migrations/0006_fix_tectonic_tree_fields.py index 40ceee06aa2..43b8aa9982b 100644 --- a/specifyweb/specify/migrations/0006_fix_tectonic_tree_fields.py +++ b/specifyweb/specify/migrations/0006_fix_tectonic_tree_fields.py @@ -108,4 +108,14 @@ class Migration(migrations.Migration): name='id', field=models.AutoField(db_column='tectonicunittreedefitemid', primary_key=True, serialize=False), ), + migrations.RemoveField( + model_name='discipline', + name='tectonicunittreedef', + ), + migrations.AddField( + model_name='discipline', + name='tectonicunittreedef', + field=models.ForeignKey(db_column='TectonicUnitTreeDefID', default=None, null=True, on_delete=specifyweb.specify.models.protect_with_blockers, related_name='disciplines', to='specify.tectonicunittreedef'), + preserve_default=False, + ), ] From c68699875099d7de03fd9a50efd597fa8e007f76 Mon Sep 17 00:00:00 2001 From: Sharad S Date: Wed, 9 Oct 2024 14:12:08 -0400 Subject: [PATCH 19/34] Use model from apps in migrations --- .../migrations/0002_default_unique_rules.py | 3 +- specifyweb/specify/migrations/0002_geo.py | 66 +++++++++++-------- 2 files changed, 40 insertions(+), 29 deletions(-) diff --git a/specifyweb/businessrules/migrations/0002_default_unique_rules.py b/specifyweb/businessrules/migrations/0002_default_unique_rules.py index 161d1b2f39a..74ecdf2b91d 100644 --- a/specifyweb/businessrules/migrations/0002_default_unique_rules.py +++ b/specifyweb/businessrules/migrations/0002_default_unique_rules.py @@ -5,7 +5,8 @@ def apply_rules_to_discipline(apps, schema_editor): - for disp in spmodels.Discipline.objects.all(): + Discipline = apps.get_model('specify', 'Discipline') + for disp in Discipline.objects.all(): apply_default_uniqueness_rules(disp) diff --git a/specifyweb/specify/migrations/0002_geo.py b/specifyweb/specify/migrations/0002_geo.py index e44b6383be0..f2a1661de0b 100644 --- a/specifyweb/specify/migrations/0002_geo.py +++ b/specifyweb/specify/migrations/0002_geo.py @@ -5,15 +5,7 @@ import django.utils.timezone from specifyweb.businessrules.exceptions import BusinessRuleException from specifyweb.specify.models import ( - protect_with_blockers, - Collectionobject, - Collectionobjecttype, - Collection, - Discipline, - Institution, - Picklist, - Picklistitem, - Taxontreedef + protect_with_blockers ) from specifyweb.specify.update_schema_config import ( update_table_schema_config_with_defaults, @@ -57,7 +49,10 @@ 'Drill Core', ] -def create_default_collection_types(): +def create_default_collection_types(apps): + Collection = apps.get_model('specify', 'Collection') + Collectionobject = apps.get_model('specify', 'Collectionobject') + Collectionobjecttype = apps.get_model('specify', 'Collectionobjecttype') # Create default collection types for each collection, named after the discipline for collection in Collection.objects.all(): discipline = collection.discipline @@ -84,15 +79,18 @@ def create_default_collection_types(): collection.save() continue -def revert_default_collection_types(): +def revert_default_collection_types(apps): # Reverse handeled by table deletion. pass -def revert_default_cog_types(): +def revert_default_cog_types(apps): # Reverse handeled by table deletion pass -def create_default_discipline_for_tree_defs(): +def create_default_discipline_for_tree_defs(apps): + Discipline = apps.get_model('specify', 'Discipline') + Institution = apps.get_model('specify', 'Institution') + for discipline in Discipline.objects.all(): geography_tree_def = discipline.geographytreedef geography_tree_def.discipline = discipline @@ -115,20 +113,25 @@ def create_default_discipline_for_tree_defs(): storage_tree_def.institution = institution storage_tree_def.save() -def revert_default_discipline_for_tree_defs(): +def revert_default_discipline_for_tree_defs(apps): # Reverse handeled by table deletion pass -def create_table_schema_config_with_defaults(): +def create_table_schema_config_with_defaults(apps): + Discipline = apps.get_model('specify', 'Discipline') for discipline in Discipline.objects.all(): for table, desc in SCHEMA_CONFIG_TABLES: update_table_schema_config_with_defaults(table, discipline.id, discipline, desc) -def revert_table_schema_config_with_defaults(): +def revert_table_schema_config_with_defaults(apps): for table, _ in SCHEMA_CONFIG_TABLES: revert_table_schema_config(table) -def create_default_collection_object_types(): +def create_default_collection_object_types(apps): + Collection = apps.get_model('specify', 'Collection') + Picklist = apps.get_model('specify', 'Picklist') + Picklistitem = apps.get_model('specify', 'Picklistitem') + for collection in Collection.objects.all(): cog_type_picklist = Picklist.objects.create( name='Default Collection Object Group Types', @@ -145,7 +148,11 @@ def create_default_collection_object_types(): picklist=cog_type_picklist ) -def revert_default_collection_object_types(): +def revert_default_collection_object_types(apps): + Collection = apps.get_model('specify', 'Collection') + Picklist = apps.get_model('specify', 'Picklist') + Picklistitem = apps.get_model('specify', 'Picklistitem') + for collection in Collection.objects.all(): cog_type_picklist_qs = Picklist.objects.filter( name='Default Collection Object Group Types', @@ -157,7 +164,10 @@ def revert_default_collection_object_types(): Picklistitem.objects.filter(picklist=cog_type_picklist).delete() cog_type_picklist.delete() -def set_discipline_for_taxon_treedefs(): +def set_discipline_for_taxon_treedefs(apps): + Collectionobjecttype = apps.get_model('specify', 'Collectionobjecttype') + Taxontreedef = apps.get_model('specify', 'Taxontreedef') + collection_object_types = Collectionobjecttype.objects.filter( taxontreedef__discipline__isnull=True ).annotate( @@ -176,17 +186,17 @@ class Migration(migrations.Migration): ] def consolidated_python_django_migration_operations(apps, schema_editor): - create_default_collection_types() - create_default_discipline_for_tree_defs() - create_table_schema_config_with_defaults() - create_default_collection_object_types() - set_discipline_for_taxon_treedefs() + create_default_collection_types(apps) + create_default_discipline_for_tree_defs(apps) + create_table_schema_config_with_defaults(apps) + create_default_collection_object_types(apps) + set_discipline_for_taxon_treedefs(apps) def revert_cosolidated_python_django_migration_operations(apps, schema_editor): - revert_default_collection_object_types() - revert_table_schema_config_with_defaults() - revert_default_discipline_for_tree_defs() - revert_default_collection_types() + revert_default_collection_object_types(apps) + revert_table_schema_config_with_defaults(apps) + revert_default_discipline_for_tree_defs(apps) + revert_default_collection_types(apps) operations = [ migrations.CreateModel( From da80e328f40c41394d7edd11a00d57b795dc162f Mon Sep 17 00:00:00 2001 From: Sharad S Date: Wed, 9 Oct 2024 14:33:25 -0400 Subject: [PATCH 20/34] Clean up migration code --- .../migrations/0002_default_unique_rules.py | 1 - .../migrations/0006_fix_tectonic_tree_fields.py | 10 ---------- 2 files changed, 11 deletions(-) diff --git a/specifyweb/businessrules/migrations/0002_default_unique_rules.py b/specifyweb/businessrules/migrations/0002_default_unique_rules.py index 74ecdf2b91d..1a3b0d2d563 100644 --- a/specifyweb/businessrules/migrations/0002_default_unique_rules.py +++ b/specifyweb/businessrules/migrations/0002_default_unique_rules.py @@ -1,6 +1,5 @@ from django.db import migrations -from specifyweb.specify import models as spmodels from specifyweb.businessrules.uniqueness_rules import apply_default_uniqueness_rules diff --git a/specifyweb/specify/migrations/0006_fix_tectonic_tree_fields.py b/specifyweb/specify/migrations/0006_fix_tectonic_tree_fields.py index 43b8aa9982b..40ceee06aa2 100644 --- a/specifyweb/specify/migrations/0006_fix_tectonic_tree_fields.py +++ b/specifyweb/specify/migrations/0006_fix_tectonic_tree_fields.py @@ -108,14 +108,4 @@ class Migration(migrations.Migration): name='id', field=models.AutoField(db_column='tectonicunittreedefitemid', primary_key=True, serialize=False), ), - migrations.RemoveField( - model_name='discipline', - name='tectonicunittreedef', - ), - migrations.AddField( - model_name='discipline', - name='tectonicunittreedef', - field=models.ForeignKey(db_column='TectonicUnitTreeDefID', default=None, null=True, on_delete=specifyweb.specify.models.protect_with_blockers, related_name='disciplines', to='specify.tectonicunittreedef'), - preserve_default=False, - ), ] From c05c215b413af95847b65f01c9d0ffa0e2adc2a4 Mon Sep 17 00:00:00 2001 From: Sharad S Date: Wed, 9 Oct 2024 17:29:23 -0400 Subject: [PATCH 21/34] Add tectonic unit to model extras --- specifyweb/specify/model_extras.py | 8 ++++++++ specifyweb/specify/models.py | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/specifyweb/specify/model_extras.py b/specifyweb/specify/model_extras.py index 82c1492fa84..17e02b5219e 100644 --- a/specifyweb/specify/model_extras.py +++ b/specifyweb/specify/model_extras.py @@ -173,6 +173,10 @@ class Lithostrat(Tree): class Meta: abstract = True +class Tectonicunit(Tree): + class Meta: + abstract = True + class Geographytreedefitem(TreeRank): class Meta: abstract = True @@ -192,3 +196,7 @@ class Meta: class Taxontreedefitem(TreeRank): class Meta: abstract = True + +class Tectonicunittreedefitem(TreeRank): + class Meta: + abstract = True diff --git a/specifyweb/specify/models.py b/specifyweb/specify/models.py index bf9a386cb25..2372009f2ec 100644 --- a/specifyweb/specify/models.py +++ b/specifyweb/specify/models.py @@ -7833,7 +7833,7 @@ class Meta: save = partialmethod(custom_save) -class Tectonicunittreedefitem(models.Model): +class Tectonicunittreedefitem(model_extras.Tectonicunittreedefitem): specify_model = datamodel.get_table('tectonicUnittreedefitem') # ID Field @@ -7865,7 +7865,7 @@ class Meta: save = partialmethod(custom_save) -class Tectonicunit(models.Model): +class Tectonicunit(model_extras.Tectonicunit): specify_model = datamodel.get_table('tectonicunit') # ID Field From 6a983ce0227ce537f850da287635a3c8f80cd7ef Mon Sep 17 00:00:00 2001 From: Sharad S Date: Thu, 10 Oct 2024 15:40:56 -0400 Subject: [PATCH 22/34] Add geo trees variable --- specifyweb/specify/tree_views.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/specifyweb/specify/tree_views.py b/specifyweb/specify/tree_views.py index 0fddcf38b7e..55c0436aaf9 100644 --- a/specifyweb/specify/tree_views.py +++ b/specifyweb/specify/tree_views.py @@ -28,8 +28,10 @@ TREE_TABLE = Literal['Taxon', 'Storage', 'Geography', 'Geologictimeperiod', 'Lithostrat'] +GEO_TREES: Tuple[TREE_TABLE, ...] = ['Tectonicunit'] + COMMON_TREES: Tuple[TREE_TABLE, ...] = ['Taxon', 'Storage', - 'Geography', 'Tectonicunit'] + 'Geography', *GEO_TREES] ALL_TRESS: Tuple[TREE_TABLE, ...] = [ *COMMON_TREES, 'Geologictimeperiod', 'Lithostrat'] From 49471bc00e5b2870c37c5a5505f267bea277bbcf Mon Sep 17 00:00:00 2001 From: melton-jason Date: Tue, 15 Oct 2024 02:30:59 -0500 Subject: [PATCH 23/34] Improve some migrations --- .../migrations/0002_default_unique_rules.py | 46 ++++++++++++++- specifyweb/businessrules/uniqueness_rules.py | 58 ++++++++++++++----- specifyweb/specify/api.py | 10 +++- specifyweb/specify/migrations/0002_geo.py | 5 +- .../migrations/0004_stratigraphy_age.py | 16 ++--- specifyweb/specify/update_schema_config.py | 41 ++++++++----- 6 files changed, 132 insertions(+), 44 deletions(-) diff --git a/specifyweb/businessrules/migrations/0002_default_unique_rules.py b/specifyweb/businessrules/migrations/0002_default_unique_rules.py index 1a3b0d2d563..f8cc544bf06 100644 --- a/specifyweb/businessrules/migrations/0002_default_unique_rules.py +++ b/specifyweb/businessrules/migrations/0002_default_unique_rules.py @@ -1,14 +1,53 @@ from django.db import migrations -from specifyweb.businessrules.uniqueness_rules import apply_default_uniqueness_rules +from specifyweb.specify.datamodel import datamodel +from specifyweb.businessrules.uniqueness_rules import apply_default_uniqueness_rules, rule_is_global, DEFAULT_UNIQUENESS_RULES -def apply_rules_to_discipline(apps, schema_editor): +def apply_default_rules(apps, schema_editor): Discipline = apps.get_model('specify', 'Discipline') for disp in Discipline.objects.all(): apply_default_uniqueness_rules(disp) +def remove_default_rules(apps, schema_editor): + Discipline = apps.get_model('specify', 'Discipline') + UniquenessRule = apps.get_model('businessrules', 'UniquenessRule') + UniquenessRuleFields = apps.get_model( + 'businessrules', 'UniquenessRuleField') + + for discipline in Discipline.objects.all(): + remove_rules_from_discipline( + discipline, UniquenessRule, UniquenessRuleFields) + + +def remove_rules_from_discipline(discipline, uniqueness_rule, uniquenessrule_fields): + for table, rules in DEFAULT_UNIQUENESS_RULES.items(): + model_name = datamodel.get_table_strict(table).django_name + for rule in rules: + to_remove = set() + fields, scopes = rule["rule"] + isDatabaseConstraint = rule["isDatabaseConstraint"] + + is_global = rule_is_global(scopes) + + for field in fields: + found_fields = uniquenessrule_fields.objects.filter(uniquenessrule__modelName=model_name, uniquenessrule__isDatabaseConstraint=isDatabaseConstraint, + uniquenessrule__discipline_id=None if is_global else discipline.id, fieldPath=field, isScope=False) + + to_remove.update( + tuple(found_fields.values_list('uniquenessrule_id', flat=True))) + found_fields.delete() + for scope in scopes: + found_scopes = uniquenessrule_fields.objects.filter(uniquenessrule__modelName=model_name, uniquenessrule__isDatabaseConstraint=isDatabaseConstraint, + uniquenessrule__discipline_id=None if is_global else discipline.id, fieldPath=scope, isScope=True) + + to_remove.update( + tuple(found_scopes.values_list('uniquenessrule_id', flat=True))) + found_scopes.delete() + uniqueness_rule.objects.filter(id__in=tuple(to_remove)).delete() + + class Migration(migrations.Migration): initial = True @@ -18,5 +57,6 @@ class Migration(migrations.Migration): ] operations = [ - migrations.RunPython(apply_rules_to_discipline), + migrations.RunPython(apply_default_rules, + remove_default_rules, atomic=True), ] diff --git a/specifyweb/businessrules/uniqueness_rules.py b/specifyweb/businessrules/uniqueness_rules.py index b2cf9c1318a..ed5d2052f46 100644 --- a/specifyweb/businessrules/uniqueness_rules.py +++ b/specifyweb/businessrules/uniqueness_rules.py @@ -6,13 +6,13 @@ from django.db import connections from django.db.migrations.recorder import MigrationRecorder from django.core.exceptions import ObjectDoesNotExist -from specifyweb.specify import models +from specifyweb.specify.api import get_model from specifyweb.specify.datamodel import datamodel from specifyweb.middleware.general import serialize_django_obj from specifyweb.specify.scoping import in_same_scope from .orm_signal_handler import orm_signal_handler from .exceptions import BusinessRuleException -from .models import UniquenessRule +from . import models DEFAULT_UNIQUENESS_RULES: Dict[str, List[Dict[str, Union[List[List[str]], bool]]]] = json.load( open('specifyweb/businessrules/uniqueness_rules.json')) @@ -29,7 +29,13 @@ @orm_signal_handler('pre_save', None, dispatch_uid=UNIQUENESS_DISPATCH_UID) def check_unique(model, instance): model_name = instance.__class__.__name__ - rules = UniquenessRule.objects.filter(modelName=model_name) + cannonical_model = get_model(model_name) + + if not cannonical_model: + # The model is not a Specify Model + # probably a Django-specific model + return + applied_migrations = MigrationRecorder( connections['default']).applied_migrations() @@ -40,14 +46,24 @@ def check_unique(model, instance): else: return + registry = None + if not model.__mro__[0] is cannonical_model.__mro__[0]: + registry = model._meta.apps + + UniquenessRule = registry.get_model( + 'businessrules', 'UniquenessRule') if registry else models.UniquenessRule + UniquenessRuleField = registry.get_model( + 'businessrules', 'UniquenessRuleField') if registry else models.UniquenessRuleField + rules = UniquenessRule.objects.filter(modelName=model_name) for rule in rules: - if not rule_is_global(tuple(field.fieldPath for field in rule.fields.filter(isScope=True))) and not in_same_scope(rule, instance): + rule_fields = UniquenessRuleField.objects.filter(uniquenessrule=rule) + if not rule_is_global(tuple(field.fieldPath for field in rule_fields.filter(isScope=True))) and not in_same_scope(rule, instance): continue field_names = [ - field.fieldPath.lower() for field in rule.fields.filter(isScope=False)] + field.fieldPath.lower() for field in rule_fields.filter(isScope=False)] - _scope = rule.fields.filter(isScope=True) + _scope = rule_fields.filter(isScope=True) scope = None if len(_scope) == 0 else _scope[0] all_fields = [*field_names] @@ -138,7 +154,9 @@ def join_with_and(fields): return ' and '.join(fields) -def apply_default_uniqueness_rules(discipline: models.Discipline): +def apply_default_uniqueness_rules(discipline, registry=None): + UniquenessRule = registry.get_model( + 'businessrules', 'UniquenessRule') if registry else models.UniquenessRule has_set_global_rules = len( UniquenessRule.objects.filter(discipline=None)) > 0 @@ -156,15 +174,23 @@ def apply_default_uniqueness_rules(discipline: models.Discipline): _discipline = None create_uniqueness_rule( - model_name, _discipline, isDatabaseConstraint, fields, scopes) - - -def create_uniqueness_rule(model_name, discipline, is_database_constraint, fields, scopes) -> UniquenessRule: - created_rule = UniquenessRule.objects.create(discipline=discipline, - modelName=model_name, isDatabaseConstraint=is_database_constraint) - created_rule.fields.set(fields) - created_rule.fields.add( - *scopes, through_defaults={"isScope": True}) + model_name, _discipline, isDatabaseConstraint, fields, scopes, registry) + + +def create_uniqueness_rule(model_name, discipline, is_database_constraint, fields, scopes, registry=None): + UniquenessRule = registry.get_model( + 'businessrules', 'UniquenessRule') if registry else models.UniquenessRule + UniquenessRuleField = registry.get_model( + 'businessrules', 'UniquenessRuleField') if registry else models.UniquenessRuleField + + created_rule, _ = UniquenessRule.objects.get_or_create(discipline_id=discipline.id if discipline else None, + modelName=model_name, isDatabaseConstraint=is_database_constraint) + for field in fields: + UniquenessRuleField.objects.get_or_create( + uniquenessrule=created_rule, fieldPath=field, isScope=False) + for scope in scopes: + UniquenessRuleField.objects.get_or_create( + uniquenessrule=created_rule, fieldPath=scope, isScope=True) """If a uniqueness rule has a scope which traverses through a hiearchy diff --git a/specifyweb/specify/api.py b/specifyweb/specify/api.py index 36221dd7bc4..a315700eca4 100644 --- a/specifyweb/specify/api.py +++ b/specifyweb/specify/api.py @@ -36,7 +36,7 @@ # Regex matching api uris for extracting the model name and id number. URI_RE = re.compile(r'^/api/specify/(\w+)/($|(\d+))') -def get_model(name: str): +def strict_get_model(name: str): """Fetch an ORM model from the module dynamically so that the typechecker doesn't complain. """ @@ -50,6 +50,12 @@ def get_model(name: str): if model._meta.model_name == name: return model raise e + +def get_model(name: str): + try: + return strict_get_model(name) + except AttributeError: + return None def correct_field_name(model, field_name: str, ignore_properties: bool = True) -> str: """Return the correct field name for a model given a case insensitive @@ -301,7 +307,7 @@ def collection_dispatch_bulk_copy(request, model, copies) -> HttpResponse: def get_model_or_404(name: str): """Lookup a specify model by name. Raise Http404 if not found.""" try: - return get_model(name) + return strict_get_model(name) except AttributeError as e: raise Http404(e) diff --git a/specifyweb/specify/migrations/0002_geo.py b/specifyweb/specify/migrations/0002_geo.py index f2a1661de0b..8ffe5917e67 100644 --- a/specifyweb/specify/migrations/0002_geo.py +++ b/specifyweb/specify/migrations/0002_geo.py @@ -121,11 +121,11 @@ def create_table_schema_config_with_defaults(apps): Discipline = apps.get_model('specify', 'Discipline') for discipline in Discipline.objects.all(): for table, desc in SCHEMA_CONFIG_TABLES: - update_table_schema_config_with_defaults(table, discipline.id, discipline, desc) + update_table_schema_config_with_defaults(table, discipline.id, desc, apps) def revert_table_schema_config_with_defaults(apps): for table, _ in SCHEMA_CONFIG_TABLES: - revert_table_schema_config(table) + revert_table_schema_config(table, apps) def create_default_collection_object_types(apps): Collection = apps.get_model('specify', 'Collection') @@ -183,6 +183,7 @@ class Migration(migrations.Migration): dependencies = [ ('specify', '0001_initial'), + ('businessrules', '0001_initial') ] def consolidated_python_django_migration_operations(apps, schema_editor): diff --git a/specifyweb/specify/migrations/0004_stratigraphy_age.py b/specifyweb/specify/migrations/0004_stratigraphy_age.py index 8115d9fe2aa..dec5ae488cd 100644 --- a/specifyweb/specify/migrations/0004_stratigraphy_age.py +++ b/specifyweb/specify/migrations/0004_stratigraphy_age.py @@ -56,7 +56,7 @@ def create_agetype_picklist(apps): picklist=age_type_picklist ) -def revert_agetype_picklist(apps, schema_editor): +def revert_agetype_picklist(apps): Collection = apps.get_model('specify', 'Collection') Picklist = apps.get_model('specify', 'Picklist') PicklistItem = apps.get_model('specify', 'Picklistitem') @@ -72,18 +72,18 @@ def create_table_schema_config_with_defaults(apps): Discipline = specify_apps.get_model('specify', 'Discipline') for discipline in Discipline.objects.all(): for table, desc in SCHEMA_CONFIG_TABLES: - update_table_schema_config_with_defaults(table, discipline.id, discipline, desc) + update_table_schema_config_with_defaults(table, discipline.id, desc, apps) for table, fields in SCHEMA_CONFIG_MOD_TABLE_FIELDS.items(): for field in fields: - update_table_field_schema_config_with_defaults(table, discipline.id, discipline, field) + update_table_field_schema_config_with_defaults(table, discipline.id, field, apps) -def revert_table_schema_config_with_defaults(): +def revert_table_schema_config_with_defaults(apps): for table, _ in SCHEMA_CONFIG_TABLES: - revert_table_schema_config(table) + revert_table_schema_config(table, apps) for table, fields in SCHEMA_CONFIG_MOD_TABLE_FIELDS.items(): for field in fields: - revert_table_field_schema_config(table, field) + revert_table_field_schema_config(table, field, apps) class Migration(migrations.Migration): @@ -96,8 +96,8 @@ def consolidated_python_django_migration_operations(apps, schema_editor): create_agetype_picklist(apps) def revert_cosolidated_python_django_migration_operations(apps, schema_editor): - revert_table_schema_config_with_defaults() - revert_agetype_picklist(apps, schema_editor) + revert_table_schema_config_with_defaults(apps) + revert_agetype_picklist(apps) operations = [ migrations.CreateModel( diff --git a/specifyweb/specify/update_schema_config.py b/specifyweb/specify/update_schema_config.py index 51d84890e85..3880c6c521e 100644 --- a/specifyweb/specify/update_schema_config.py +++ b/specifyweb/specify/update_schema_config.py @@ -1,14 +1,14 @@ import re + +from typing import NamedTuple + +from django.db.models import Q +from django.apps import apps + from specifyweb.specify.load_datamodel import Table from specifyweb.specify.models import ( - Splocalecontainer, - Splocalecontaineritem, - Splocaleitemstr, - Discipline, datamodel, ) -from typing import List, Optional, NamedTuple -from django.db.models import Q HIDDEN_FIELDS = [ "timestampcreated", "timestampmodified", "version", "createdbyagent", "modifiedbyagent" @@ -31,9 +31,13 @@ class FieldSchemaConfig(NamedTuple): def update_table_schema_config_with_defaults( table_name, discipline_id: int, - discipline: Optional[Discipline], description: str = None, + apps = apps ): + Splocalecontainer = apps.get_model('specify', 'Splocalecontainer') + Splocaleitemstr = apps.get_model('specify', 'Splocaleitemstr') + Splocalecontaineritem = apps.get_model('specify', 'Splocalecontaineritem') + table: Table = datamodel.get_table(table_name) table_name = table.name table_desc = re.sub(r'(? Date: Tue, 15 Oct 2024 11:06:28 -0400 Subject: [PATCH 24/34] change isLegacy to false --- .../js_src/lib/components/InitialContext/remotePrefs.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specifyweb/frontend/js_src/lib/components/InitialContext/remotePrefs.ts b/specifyweb/frontend/js_src/lib/components/InitialContext/remotePrefs.ts index d90d72ad737..cefcfc5dab6 100644 --- a/specifyweb/frontend/js_src/lib/components/InitialContext/remotePrefs.ts +++ b/specifyweb/frontend/js_src/lib/components/InitialContext/remotePrefs.ts @@ -160,7 +160,7 @@ export const remotePrefsDefinitions = f.store( description: 'Sort order for nodes in the tree viewer', defaultValue: 'name', formatters: [formatter.trim], - isLegacy: true, + isLegacy: false, }, 'TreeEditor.Rank.Threshold.GeologicTimePeriod': { description: From 1e1c0c3b03acf9cd049191fd968c20ca79983a39 Mon Sep 17 00:00:00 2001 From: melton-jason Date: Tue, 15 Oct 2024 12:14:40 -0500 Subject: [PATCH 25/34] Simplify resolving registry for uniqueness rules --- specifyweb/businessrules/uniqueness_rules.py | 11 +++++------ .../migrations/0004_rename_merge_policy.py | 17 +++++++++++++++-- specifyweb/specify/migrations/0002_geo.py | 1 - 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/specifyweb/businessrules/uniqueness_rules.py b/specifyweb/businessrules/uniqueness_rules.py index ed5d2052f46..5e7f8aeb6e0 100644 --- a/specifyweb/businessrules/uniqueness_rules.py +++ b/specifyweb/businessrules/uniqueness_rules.py @@ -3,6 +3,7 @@ import json from typing import Dict, List, Union, Iterable +from django.apps import apps from django.db import connections from django.db.migrations.recorder import MigrationRecorder from django.core.exceptions import ObjectDoesNotExist @@ -46,15 +47,13 @@ def check_unique(model, instance): else: return - registry = None - if not model.__mro__[0] is cannonical_model.__mro__[0]: - registry = model._meta.apps + registry = model._meta.apps - UniquenessRule = registry.get_model( - 'businessrules', 'UniquenessRule') if registry else models.UniquenessRule + UniquenessRule = registry.get_model('businessrules', 'UniquenessRule') UniquenessRuleField = registry.get_model( - 'businessrules', 'UniquenessRuleField') if registry else models.UniquenessRuleField + 'businessrules', 'UniquenessRuleField') rules = UniquenessRule.objects.filter(modelName=model_name) + # raise KeyError(model_name, rules, UniquenessRule.objects.all()) for rule in rules: rule_fields = UniquenessRuleField.objects.filter(uniquenessrule=rule) if not rule_is_global(tuple(field.fieldPath for field in rule_fields.filter(isScope=True))) and not in_same_scope(rule, instance): diff --git a/specifyweb/notifications/migrations/0004_rename_merge_policy.py b/specifyweb/notifications/migrations/0004_rename_merge_policy.py index 9fcc9748705..bd3cc18d930 100644 --- a/specifyweb/notifications/migrations/0004_rename_merge_policy.py +++ b/specifyweb/notifications/migrations/0004_rename_merge_policy.py @@ -1,6 +1,6 @@ from django.db import migrations -def initialize(apps, schema_editor): +def apply_migration(apps, schema_editor): UserPolicy = apps.get_model('permissions', 'UserPolicy') LibraryRolePolicy = apps.get_model('permissions', 'LibraryRolePolicy') RolePolicy = apps.get_model('permissions', 'RolePolicy') @@ -13,6 +13,19 @@ def initialize(apps, schema_editor): role_policies.update(resource="/record/merge") library_role_policies.update(resource="/record/merge") +def revert_migration(apps, schema_editor): + UserPolicy = apps.get_model('permissions', 'UserPolicy') + LibraryRolePolicy = apps.get_model('permissions', 'LibraryRolePolicy') + RolePolicy = apps.get_model('permissions', 'RolePolicy') + + user_policies = UserPolicy.objects.filter(resource="/record/merge") + role_policies = RolePolicy.objects.filter(resource="/record/merge") + library_role_policies = LibraryRolePolicy.objects.filter(resource="/record/merge") + + user_policies.update(resource='/record/replace') + role_policies.update(resource='/record/replace') + library_role_policies.update(resource='/record/replace') + class Migration(migrations.Migration): dependencies = [ ('permissions', '0005_merge_20220414_1451'), @@ -20,5 +33,5 @@ class Migration(migrations.Migration): ] operations = [ - migrations.RunPython(initialize), + migrations.RunPython(apply_migration, revert_migration, atomic=True), ] diff --git a/specifyweb/specify/migrations/0002_geo.py b/specifyweb/specify/migrations/0002_geo.py index 8ffe5917e67..2ffa00dc730 100644 --- a/specifyweb/specify/migrations/0002_geo.py +++ b/specifyweb/specify/migrations/0002_geo.py @@ -183,7 +183,6 @@ class Migration(migrations.Migration): dependencies = [ ('specify', '0001_initial'), - ('businessrules', '0001_initial') ] def consolidated_python_django_migration_operations(apps, schema_editor): From 0dcaaa17e96c13a94010432080c113a21ca86107 Mon Sep 17 00:00:00 2001 From: melton-jason Date: Tue, 15 Oct 2024 12:16:02 -0500 Subject: [PATCH 26/34] Remove comment --- specifyweb/businessrules/uniqueness_rules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specifyweb/businessrules/uniqueness_rules.py b/specifyweb/businessrules/uniqueness_rules.py index 5e7f8aeb6e0..22dec4ce983 100644 --- a/specifyweb/businessrules/uniqueness_rules.py +++ b/specifyweb/businessrules/uniqueness_rules.py @@ -52,8 +52,8 @@ def check_unique(model, instance): UniquenessRule = registry.get_model('businessrules', 'UniquenessRule') UniquenessRuleField = registry.get_model( 'businessrules', 'UniquenessRuleField') + rules = UniquenessRule.objects.filter(modelName=model_name) - # raise KeyError(model_name, rules, UniquenessRule.objects.all()) for rule in rules: rule_fields = UniquenessRuleField.objects.filter(uniquenessrule=rule) if not rule_is_global(tuple(field.fieldPath for field in rule_fields.filter(isScope=True))) and not in_same_scope(rule, instance): From 94687062607f561a66e5d0612688df5bfc987a73 Mon Sep 17 00:00:00 2001 From: melton-jason Date: Tue, 15 Oct 2024 12:19:15 -0500 Subject: [PATCH 27/34] Add documentation --- specifyweb/businessrules/uniqueness_rules.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specifyweb/businessrules/uniqueness_rules.py b/specifyweb/businessrules/uniqueness_rules.py index 22dec4ce983..e05e3a22dc7 100644 --- a/specifyweb/businessrules/uniqueness_rules.py +++ b/specifyweb/businessrules/uniqueness_rules.py @@ -3,7 +3,6 @@ import json from typing import Dict, List, Union, Iterable -from django.apps import apps from django.db import connections from django.db.migrations.recorder import MigrationRecorder from django.core.exceptions import ObjectDoesNotExist @@ -47,12 +46,13 @@ def check_unique(model, instance): else: return + # We can't directly use the main app registry in the context of migrations, which uses fake models registry = model._meta.apps UniquenessRule = registry.get_model('businessrules', 'UniquenessRule') UniquenessRuleField = registry.get_model( 'businessrules', 'UniquenessRuleField') - + rules = UniquenessRule.objects.filter(modelName=model_name) for rule in rules: rule_fields = UniquenessRuleField.objects.filter(uniquenessrule=rule) From 22a144201b3100356cf570ad4562e604798ce559 Mon Sep 17 00:00:00 2001 From: Sharad S Date: Tue, 15 Oct 2024 15:23:09 -0400 Subject: [PATCH 28/34] Add link between paleoContext -> tectonicUnit --- .../lib/components/QueryBuilder/fromTree.ts | 17 +++++++++++++++-- specifyweb/specify/datamodel.py | 1 + 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/specifyweb/frontend/js_src/lib/components/QueryBuilder/fromTree.ts b/specifyweb/frontend/js_src/lib/components/QueryBuilder/fromTree.ts index 5b04dea474b..86f5faf9d7e 100644 --- a/specifyweb/frontend/js_src/lib/components/QueryBuilder/fromTree.ts +++ b/specifyweb/frontend/js_src/lib/components/QueryBuilder/fromTree.ts @@ -152,15 +152,28 @@ const defaultFields: RR< : []), ]; }, - TectonicUnit: async (_nodeId, _rankName) => { + TectonicUnit: async (nodeId, rankName) => { // TODO: Fields below are a placeholder. Remove once we determine the requirements for querying Tectonic trees + const paleoPath = await fetchPaleoPath(); return [ makeField('catalogNumber', {}), - makeField('determinations.taxon.fullName', {}), + makeField('determinations.taxon.fullName', { + sortType: flippedSortTypes.ascending, + }), makeField('determinations.isCurrent', { isDisplay: false, operStart: queryFieldFilters.trueOrNull.id, }), + ...(typeof paleoPath === 'string' + ? [ + makeField(`${paleoPath}.tectonicUnit.fullName`, {}), + makeField(`${paleoPath}.tectonicUnit.${rankName}.lithoStratId`, { + operStart: queryFieldFilters.equal.id, + startValue: nodeId.toString(), + isDisplay: false, + }), + ] + : []), ]; }, }; diff --git a/specifyweb/specify/datamodel.py b/specifyweb/specify/datamodel.py index c9ecebfd817..5fc49bbecb7 100644 --- a/specifyweb/specify/datamodel.py +++ b/specifyweb/specify/datamodel.py @@ -5157,6 +5157,7 @@ Relationship(name='createdByAgent', type='many-to-one',required=False, relatedModelName='Agent', column='CreatedByAgentID'), Relationship(name='discipline', type='many-to-one',required=True, relatedModelName='Discipline', column='DisciplineID'), Relationship(name='lithoStrat', type='many-to-one',required=False, relatedModelName='LithoStrat', column='LithoStratID', otherSideName='paleoContexts'), + Relationship(name='tectonicUnit', type='many-to-one',required=False, relatedModelName='TectonicUnit', column='TectonicUnitID', otherSideName='paleoContexts'), Relationship(name='localities', type='one-to-many',required=False, relatedModelName='Locality', otherSideName='paleoContext'), Relationship(name='modifiedByAgent', type='many-to-one',required=False, relatedModelName='Agent', column='ModifiedByAgentID') ], From b63d7e5f5c80963d35e5464c67da8a64e7e9b170 Mon Sep 17 00:00:00 2001 From: Sharad S Date: Tue, 15 Oct 2024 15:25:35 -0400 Subject: [PATCH 29/34] Add tectonic unit to tree_table list --- specifyweb/specify/tree_views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specifyweb/specify/tree_views.py b/specifyweb/specify/tree_views.py index 55c0436aaf9..36c780aae2b 100644 --- a/specifyweb/specify/tree_views.py +++ b/specifyweb/specify/tree_views.py @@ -26,7 +26,7 @@ logger = logging.getLogger(__name__) TREE_TABLE = Literal['Taxon', 'Storage', - 'Geography', 'Geologictimeperiod', 'Lithostrat'] + 'Geography', 'Geologictimeperiod', 'Lithostrat', 'Tectonicunit'] GEO_TREES: Tuple[TREE_TABLE, ...] = ['Tectonicunit'] From f7dbe32a437d850f8649d70928087f7e1a4f6d90 Mon Sep 17 00:00:00 2001 From: Sharad S Date: Tue, 15 Oct 2024 19:26:49 +0000 Subject: [PATCH 30/34] Lint code with ESLint and Prettier Triggered by 22a144201b3100356cf570ad4562e604798ce559 on branch refs/heads/issue-5317 --- .../js_src/lib/components/InitialContext/remotePrefs.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/specifyweb/frontend/js_src/lib/components/InitialContext/remotePrefs.ts b/specifyweb/frontend/js_src/lib/components/InitialContext/remotePrefs.ts index cefcfc5dab6..c70f2292ded 100644 --- a/specifyweb/frontend/js_src/lib/components/InitialContext/remotePrefs.ts +++ b/specifyweb/frontend/js_src/lib/components/InitialContext/remotePrefs.ts @@ -280,7 +280,8 @@ export const remotePrefsDefinitions = f.store( isLegacy: false, }, 'sp7.allow_adding_child_to_synonymized_parent.TectonicUnit': { - description: 'Allowed to add children to synopsized TectonicUnit records', + description: + 'Allowed to add children to synopsized TectonicUnit records', defaultValue: false, parser: 'java.lang.Boolean', isLegacy: false, From f1092c1636a2b21f76f71542ea9152cf04f2a84d Mon Sep 17 00:00:00 2001 From: Sharad S Date: Wed, 16 Oct 2024 12:19:29 -0400 Subject: [PATCH 31/34] Remove serialize=False in models.py --- specifyweb/specify/migrations/0006_fix_tectonic_tree_fields.py | 2 +- specifyweb/specify/models.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/specifyweb/specify/migrations/0006_fix_tectonic_tree_fields.py b/specifyweb/specify/migrations/0006_fix_tectonic_tree_fields.py index 40ceee06aa2..fd95ea649a2 100644 --- a/specifyweb/specify/migrations/0006_fix_tectonic_tree_fields.py +++ b/specifyweb/specify/migrations/0006_fix_tectonic_tree_fields.py @@ -1,4 +1,4 @@ -# Generated by Django 3.2.15 on 2024-10-09 14:58 +# Generated by Django 3.2.15 on 2024-10-16 16:13 from django.db import migrations, models import specifyweb.specify.models diff --git a/specifyweb/specify/models.py b/specifyweb/specify/models.py index 2372009f2ec..d4830a119a2 100644 --- a/specifyweb/specify/models.py +++ b/specifyweb/specify/models.py @@ -7621,7 +7621,7 @@ class AbsoluteAge(models.Model): specify_model = datamodel.get_table('absoluteage') # ID Field - id = models.AutoField(db_column='AbsoluteAgeID', primary_key=True, serialize=False) + id = models.AutoField(db_column='AbsoluteAgeID', primary_key=True) # Fields absoluteage = models.DecimalField(blank=True, max_digits=22, decimal_places=10, null=True, unique=False, db_column='AbsoluteAge', db_index=False) From 8cc13cd9de29b85697ef121a275e2044f4d81b4a Mon Sep 17 00:00:00 2001 From: Sharad S Date: Thu, 17 Oct 2024 14:52:28 -0400 Subject: [PATCH 32/34] fix test case --- specifyweb/businessrules/uniqueness_rules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specifyweb/businessrules/uniqueness_rules.py b/specifyweb/businessrules/uniqueness_rules.py index e05e3a22dc7..4950caf6ad4 100644 --- a/specifyweb/businessrules/uniqueness_rules.py +++ b/specifyweb/businessrules/uniqueness_rules.py @@ -182,7 +182,7 @@ def create_uniqueness_rule(model_name, discipline, is_database_constraint, field UniquenessRuleField = registry.get_model( 'businessrules', 'UniquenessRuleField') if registry else models.UniquenessRuleField - created_rule, _ = UniquenessRule.objects.get_or_create(discipline_id=discipline.id if discipline else None, + created_rule = UniquenessRule.objects.create(discipline=discipline, modelName=model_name, isDatabaseConstraint=is_database_constraint) for field in fields: UniquenessRuleField.objects.get_or_create( From 277dbda735d85a22ee28b987385d904116cf314f Mon Sep 17 00:00:00 2001 From: melton-jason Date: Fri, 18 Oct 2024 11:55:00 -0500 Subject: [PATCH 33/34] Modify create_uniqueness_rule to skip creating same rule --- specifyweb/businessrules/uniqueness_rules.py | 23 +++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/specifyweb/businessrules/uniqueness_rules.py b/specifyweb/businessrules/uniqueness_rules.py index 4950caf6ad4..edae82d318a 100644 --- a/specifyweb/businessrules/uniqueness_rules.py +++ b/specifyweb/businessrules/uniqueness_rules.py @@ -182,14 +182,25 @@ def create_uniqueness_rule(model_name, discipline, is_database_constraint, field UniquenessRuleField = registry.get_model( 'businessrules', 'UniquenessRuleField') if registry else models.UniquenessRuleField - created_rule = UniquenessRule.objects.create(discipline=discipline, - modelName=model_name, isDatabaseConstraint=is_database_constraint) + matching_fields = UniquenessRuleField.objects.filter( + fieldPath__in=fields, uniquenessrule__modelName=model_name, uniquenessrule__isDatabaseConstraint=is_database_constraint, uniquenessrule__discipline=discipline, isScope=False) + + matching_scopes = UniquenessRuleField.objects.filter( + fieldPath__in=scopes, uniquenessrule__modelName=model_name, uniquenessrule__isDatabaseConstraint=is_database_constraint, uniquenessrule__discipline=discipline, isScope=True) + + # If the rule already exists, skip creating the rule + if len(matching_fields) == len(fields) and len(matching_scopes) == len(scopes): + return + + rule = UniquenessRule.objects.create( + discipline=discipline, modelName=model_name, isDatabaseConstraint=is_database_constraint) + for field in fields: - UniquenessRuleField.objects.get_or_create( - uniquenessrule=created_rule, fieldPath=field, isScope=False) + UniquenessRuleField.objects.create( + uniquenessrule=rule, fieldPath=field, isScope=False) for scope in scopes: - UniquenessRuleField.objects.get_or_create( - uniquenessrule=created_rule, fieldPath=scope, isScope=True) + UniquenessRuleField.objects.create( + uniquenessrule=rule, fieldPath=scope, isScope=True) """If a uniqueness rule has a scope which traverses through a hiearchy From dfe5887c006551322759ea5ada539036f5a68d57 Mon Sep 17 00:00:00 2001 From: Sharad S <16229739+sharadsw@users.noreply.github.com> Date: Mon, 21 Oct 2024 21:14:39 +0000 Subject: [PATCH 34/34] Lint code with ESLint and Prettier Triggered by 585cc739f545be48968ace880a9dda3f3eefcc5e on branch refs/heads/production --- .../js_src/lib/components/InitialContext/treeRanks.ts | 6 +++++- specifyweb/frontend/js_src/lib/components/TreeView/Tree.tsx | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/specifyweb/frontend/js_src/lib/components/InitialContext/treeRanks.ts b/specifyweb/frontend/js_src/lib/components/InitialContext/treeRanks.ts index 3ded51fad8d..c006f7d1f81 100644 --- a/specifyweb/frontend/js_src/lib/components/InitialContext/treeRanks.ts +++ b/specifyweb/frontend/js_src/lib/components/InitialContext/treeRanks.ts @@ -40,7 +40,11 @@ let treeDefinitions: TreeInformation = undefined!; const commonTrees = ['Geography', 'Storage', 'Taxon'] as const; const treesForPaleo = ['GeologicTimePeriod', 'LithoStrat'] as const; const treesForGeo = ['TectonicUnit'] as const; -export const allTrees = [...commonTrees, ...treesForPaleo, ...treesForGeo] as const; +export const allTrees = [ + ...commonTrees, + ...treesForPaleo, + ...treesForGeo, +] as const; /* * Until discipline information is loaded, assume all trees are appropriate in * this discipline diff --git a/specifyweb/frontend/js_src/lib/components/TreeView/Tree.tsx b/specifyweb/frontend/js_src/lib/components/TreeView/Tree.tsx index bec182f9eca..04b45137824 100644 --- a/specifyweb/frontend/js_src/lib/components/TreeView/Tree.tsx +++ b/specifyweb/frontend/js_src/lib/components/TreeView/Tree.tsx @@ -31,7 +31,7 @@ const treeToPref = { Storage: 'storage', GeologicTimePeriod: 'geologicTimePeriod', LithoStrat: 'lithoStrat', - TectonicUnit: 'tectonicUnit' + TectonicUnit: 'tectonicUnit', } as const; export function Tree<