From 15e614a34ae4ab221a9cf641ea49780cdeac8799 Mon Sep 17 00:00:00 2001 From: melton-jason Date: Thu, 26 Dec 2024 13:53:33 -0600 Subject: [PATCH 01/11] Add better support for one-to-one relationships Fixes #5508 Fixes #5452 Related #5463 --- .../DataModel/__tests__/resourceApi.test.ts | 14 +- .../lib/components/DataModel/collectionApi.ts | 13 +- .../lib/components/DataModel/resourceApi.ts | 47 ++-- .../js_src/lib/components/DataModel/types.ts | 202 +++++++++--------- specifyweb/specify/api.py | 11 +- specifyweb/specify/datamodel.py | 4 +- 6 files changed, 155 insertions(+), 136 deletions(-) diff --git a/specifyweb/frontend/js_src/lib/components/DataModel/__tests__/resourceApi.test.ts b/specifyweb/frontend/js_src/lib/components/DataModel/__tests__/resourceApi.test.ts index e594c6b55e7..f9511ee4ef8 100644 --- a/specifyweb/frontend/js_src/lib/components/DataModel/__tests__/resourceApi.test.ts +++ b/specifyweb/frontend/js_src/lib/components/DataModel/__tests__/resourceApi.test.ts @@ -1,7 +1,7 @@ import { overrideAjax } from '../../../tests/ajax'; import { requireContext } from '../../../tests/helpers'; import type { RA } from '../../../utils/types'; -import { replaceItem } from '../../../utils/utils'; +import { removeKey, replaceItem } from '../../../utils/utils'; import { addMissingFields } from '../addMissingFields'; import type { SerializedRecord } from '../helperTypes'; import { getResourceApiUrl } from '../resource'; @@ -181,14 +181,20 @@ describe('rgetCollection', () => { expect(agents.models).toHaveLength(0); }); - test('repeated calls for independent return different object', async () => { + test('repeated calls for independent merge different objects', async () => { const resource = new tables.CollectionObject.Resource({ id: collectionObjectId, }); + const testCEText = 'someOtherText'; + const firstCollectingEvent = await resource.rgetPromise('collectingEvent'); + firstCollectingEvent?.set('text1', testCEText); const secondCollectingEvent = await resource.rgetPromise('collectingEvent'); - expect(firstCollectingEvent?.toJSON()).toEqual(collectingEventResponse); - expect(firstCollectingEvent).not.toBe(secondCollectingEvent); + expect(testCEText).not.toBe(collectingEventText); + expect(secondCollectingEvent?.get('text1')).toBe(testCEText); + expect( + removeKey(firstCollectingEvent?.toJSON() ?? {}, 'text1') + ).toStrictEqual(removeKey(secondCollectingEvent?.toJSON() ?? {}, 'text1')); }); test('call for independent refetches related', async () => { diff --git a/specifyweb/frontend/js_src/lib/components/DataModel/collectionApi.ts b/specifyweb/frontend/js_src/lib/components/DataModel/collectionApi.ts index 8bc041d68a1..0d7e8925031 100644 --- a/specifyweb/frontend/js_src/lib/components/DataModel/collectionApi.ts +++ b/specifyweb/frontend/js_src/lib/components/DataModel/collectionApi.ts @@ -200,14 +200,13 @@ export const IndependentCollection = LazyCollection.extend({ this.on( 'change', function (resource: SpecifyResource) { - if (!resource.isBeingInitialized()) { - if (relationshipIsToMany(this.field)) { - const otherSideName = this.field.getReverse().name; - this.related.set(otherSideName, resource); - } - this.updated[resource.cid] = resource; - this.trigger('saverequired'); + if (resource.isBeingInitialized()) return; + if (relationshipIsToMany(this.field)) { + const otherSideName = this.field.getReverse().name; + this.related.set(otherSideName, resource); } + this.updated[resource.cid] = resource; + this.trigger('saverequired'); }, this ); diff --git a/specifyweb/frontend/js_src/lib/components/DataModel/resourceApi.ts b/specifyweb/frontend/js_src/lib/components/DataModel/resourceApi.ts index 2fe9e86ae5f..5d049add063 100644 --- a/specifyweb/frontend/js_src/lib/components/DataModel/resourceApi.ts +++ b/specifyweb/frontend/js_src/lib/components/DataModel/resourceApi.ts @@ -4,6 +4,7 @@ import _ from 'underscore'; import { hijackBackboneAjax } from '../../utils/ajax/backboneAjax'; import { Http } from '../../utils/ajax/definitions'; +import type { RA } from '../../utils/types'; import { removeKey } from '../../utils/utils'; import { assert } from '../Errors/assert'; import { softFail } from '../Errors/Crash'; @@ -46,9 +47,10 @@ function eventHandlerForToOne(related, field) { this.set(field.name, related.url()); return; } - case 'changing': { - this.trigger.apply(this, args); - return; + case 'changed': { + if (!related.isBeingInitialized()) { + this.set(field.name, related); + } } } @@ -65,10 +67,6 @@ function eventHandlerForToMany(related, field) { return function (event) { const args = _.toArray(arguments); switch (event) { - case 'changing': { - this.trigger.apply(this, args); - break; - } case 'saverequired': { this.handleChanged(); break; @@ -182,6 +180,7 @@ export const ResourceBase = Backbone.Model.extend({ if (exemptFields.includes(fieldName)) return; const field = self.specifyTable.getField(fieldName); switch (field.type) { + case 'one-to-one': case 'many-to-one': { /* * Many-to-one wouldn't ordinarily be dependent, but @@ -510,6 +509,7 @@ export const ResourceBase = Backbone.Model.extend({ */ return undefined; } + case 'one-to-one': case 'many-to-one': { if (!value) { /* @@ -547,12 +547,6 @@ export const ResourceBase = Backbone.Model.extend({ this.trigger('change', this); return undefined; } - /* - * Needed for taxonTreeDef on discipline because field.isVirtual equals false - */ - case 'one-to-one': { - return value; - } } if (!field.isVirtual) softFail('Unhandled setting of relationship field', { @@ -617,12 +611,12 @@ export const ResourceBase = Backbone.Model.extend({ }; return this.getRelated(fieldName, options); }, - async getRelated(fieldName, options) { + async getRelated(fieldName: RA | string, options) { options ||= { prePop: false, noBusinessRules: false, }; - const path = _(fieldName).isArray() + const path = Array.isArray(fieldName) ? fieldName : fieldName.split(backboneFieldSeparator); @@ -689,26 +683,31 @@ export const ResourceBase = Backbone.Model.extend({ // A foreign key field. if (!value) return value; // No related object - // Is the related resource a cached dependent? - let toOne = this.dependentResources[fieldName]; + // Is the related resource cached? + let toOne = + this.dependentResources[fieldName] ?? + this.independentResources[fieldName]; if (!toOne) { - if (typeof value === 'string') + if (typeof value === 'string') { toOne = resourceFromUrl(value, { noBusinessRules: options.noBusinessRules, }); - else if (field.isDependent() && typeof value === 'object') { + if (toOne === undefined) softFail('expected URI, got', value); + } else if (typeof value === 'object') { toOne = new field.relatedTable.Resource({ ...value }); - } else _(value).isString() || softFail('expected URI, got', value); + } if (field.isDependent()) { console.warn('expected dependent resource to be in cache'); this.storeDependent(field, toOne); - } else { - // Always store and refetch independent related resources - this.storeIndependent(field, toOne); } } + + // Always store and refetch independent related resources + if (!field.isDependent()) { + this.storeIndependent(field, toOne); + } // If we want a field within the related resource then recur return path.length > 1 ? toOne.rget(_.tail(path)) : toOne; } @@ -728,7 +727,7 @@ export const ResourceBase = Backbone.Model.extend({ */ // Is it already cached? - if (!_.isUndefined(this.dependentResources[fieldName])) { + if (this.dependentResources[fieldName] !== undefined) { value = this.dependentResources[fieldName]; if (value == null) return null; // Recur if we need to traverse more diff --git a/specifyweb/frontend/js_src/lib/components/DataModel/types.ts b/specifyweb/frontend/js_src/lib/components/DataModel/types.ts index b5da2fbf67f..04de8671d0f 100644 --- a/specifyweb/frontend/js_src/lib/components/DataModel/types.ts +++ b/specifyweb/frontend/js_src/lib/components/DataModel/types.ts @@ -239,20 +239,20 @@ export type Tables = { export type Accession = { readonly tableName: 'Accession'; readonly fields: { - readonly accessionCondition: string | null; readonly accessionNumber: string; + readonly accessionCondition: string | null; + readonly dateAccessioned: string | null; readonly actualTotalCountAmt: number | null; readonly collectionObjectCount: number | null; - readonly dateAccessioned: string | null; readonly dateAcknowledged: string | null; - readonly dateReceived: string | null; + readonly remarks: 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 remarks: string | null; + readonly dateReceived: string | null; readonly status: string | null; readonly text1: string | null; readonly text2: string | null; @@ -449,8 +449,8 @@ export type Agent = { readonly dateOfDeathPrecision: number | null; readonly dateType: number | null; readonly date1: string | null; - readonly date1Precision: number | null; readonly date2: string | null; + readonly date1Precision: number | null; readonly date2Precision: number | null; readonly email: string | null; readonly firstName: string | null; @@ -458,10 +458,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; @@ -494,8 +494,8 @@ export type Agent = { readonly agentAttachments: RA; readonly agentGeographies: RA; readonly agentSpecialties: RA; - readonly groups: RA; readonly identifiers: RA; + readonly groups: RA; readonly variants: RA; }; readonly toManyIndependent: { @@ -926,13 +926,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; @@ -974,22 +974,22 @@ export type BorrowReturnMaterial = { export type CollectingEvent = { readonly tableName: 'CollectingEvent'; readonly fields: { - readonly stationFieldNumber: string | null; + readonly startDate: 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 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; @@ -1076,6 +1076,10 @@ 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; @@ -1086,11 +1090,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; @@ -1100,26 +1104,22 @@ 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; @@ -1160,6 +1160,7 @@ 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; @@ -1177,8 +1178,6 @@ 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; @@ -1190,6 +1189,7 @@ 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; @@ -1365,15 +1365,17 @@ export type Collection = { export type CollectionObject = { readonly tableName: 'CollectionObject'; readonly fields: { - readonly catalogedDate: string | null; readonly actualTotalCountAmt: number | null; readonly age: string | 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 collectionMemberId: number; readonly countAmt: number | null; + readonly reservedText: string | null; + readonly timestampModified: string | null; readonly date1: string | null; readonly date1Precision: number | null; readonly deaccessioned: boolean | null; @@ -1382,47 +1384,46 @@ export type CollectionObject = { readonly embargoReleaseDatePrecision: number | null; readonly embargoStartDate: string | null; readonly embargoStartDatePrecision: number | null; - readonly fieldNumber: string | null; 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 isMemberOfCOG: boolean | null; - readonly text8: string | null; - readonly timestampModified: string | null; readonly modifier: string | null; readonly name: string | null; readonly notifications: string | null; readonly numberOfDuplicates: number | null; + readonly number1: number | null; + readonly number2: number | null; readonly objectCondition: string | null; readonly ocr: string | null; - readonly text1: string | null; - readonly yesNo1: boolean | null; + readonly altCatalogNumber: string | null; readonly projectNumber: string | null; - readonly text3: 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 catalogNumber: string | null; + readonly text1: string | null; readonly description: 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 timestampCreated: string; readonly totalCountAmt: number | null; readonly totalValue: number | null; readonly uniqueIdentifier: string | null; - readonly number1: number | 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; @@ -1438,18 +1439,18 @@ 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 paleoContext: PaleoContext | null; readonly inventorizedBy: Agent | null; - readonly modifiedByAgent: Agent | null; + readonly paleoContext: PaleoContext | null; readonly visibilitySetBy: SpecifyUser | null; }; readonly toManyDependent: { @@ -1514,20 +1515,12 @@ export type CollectionObjectAttr = { export type CollectionObjectAttribute = { readonly tableName: 'CollectionObjectAttribute'; readonly fields: { - readonly number1: number | null; readonly bottomDistance: number | null; readonly collectionMemberId: number; - readonly text7: string | null; - readonly text5: string | null; - readonly text11: string | null; - readonly number2: number | null; + readonly date1: string | null; readonly date1Precision: number | null; - readonly remarks: string | null; readonly direction: string | null; readonly distanceUnits: string | null; - readonly text10: string | null; - readonly text15: string | null; - readonly number3: number | null; readonly integer1: number | null; readonly integer10: number | null; readonly integer2: number | null; @@ -1538,16 +1531,18 @@ export type CollectionObjectAttribute = { readonly integer7: number | null; readonly integer8: number | null; readonly integer9: number | null; - readonly number10: number | null; - readonly number11: number | null; readonly number12: number | null; readonly number13: number | null; + readonly number1: number | null; + readonly number10: number | null; + readonly number11: number | null; readonly number14: number | null; readonly number15: number | null; readonly number16: number | null; readonly number17: number | null; readonly number18: number | null; readonly number19: number | null; + readonly number2: number | null; readonly number20: number | null; readonly number21: number | null; readonly number22: number | null; @@ -1558,6 +1553,7 @@ export type CollectionObjectAttribute = { readonly number27: number | null; readonly number28: number | null; readonly number29: number | null; + readonly number3: number | null; readonly number30: number | null; readonly number31: number | null; readonly number32: number | null; @@ -1565,36 +1561,42 @@ export type CollectionObjectAttribute = { readonly number34: number | null; readonly number35: number | null; readonly number36: number | null; + readonly number37: number | null; + readonly number38: number | null; readonly number39: number | null; readonly number4: number | null; readonly number40: number | null; + readonly number41: number | null; readonly number42: number | null; readonly number5: number | null; readonly number6: number | null; 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 text9: string | null; - readonly text40: string | null; - readonly text39: string | null; - readonly text6: string | null; - readonly text3: string | null; + readonly text10: string | null; + readonly remarks: string | null; readonly text8: string | null; - readonly text1: string | null; + readonly text11: 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; + readonly text23: string | null; readonly text24: string | null; readonly text25: string | null; readonly text26: string | null; readonly text27: string | null; readonly text28: string | null; readonly text29: string | null; + readonly text3: string | null; readonly text30: string | null; readonly text31: string | null; readonly text32: string | null; @@ -1604,15 +1606,19 @@ export type CollectionObjectAttribute = { readonly text36: string | null; readonly text37: string | null; readonly text38: string | null; + readonly text39: string | null; + readonly text4: string | null; + readonly text40: string | null; + readonly text5: string | null; + readonly text6: string | null; + readonly text7: string | null; + readonly text9: string | null; readonly timestampCreated: string; readonly timestampModified: string | null; - readonly topDistance: number | null; - readonly text22: string | null; - readonly date1: string | null; readonly text12: string | null; - readonly text23: string | null; + readonly topDistance: number | null; readonly version: number | null; - readonly text4: string | null; + readonly text2: string | null; readonly yesNo1: boolean | null; readonly yesNo10: boolean | null; readonly yesNo11: boolean | null; @@ -1636,9 +1642,9 @@ export type CollectionObjectAttribute = { }; readonly toOneDependent: RR; readonly toOneIndependent: { + readonly agent1: Agent | null; readonly createdByAgent: Agent | null; readonly modifiedByAgent: Agent | null; - readonly agent1: Agent | null; }; readonly toManyDependent: RR; readonly toManyIndependent: { @@ -2186,7 +2192,9 @@ 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; @@ -2196,8 +2204,6 @@ 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; @@ -2290,8 +2296,8 @@ export type DNASequencingRun = { readonly runByAgent: Agent | null; }; readonly toManyDependent: { - readonly attachments: RA; readonly citations: RA; + readonly attachments: RA; }; readonly toManyIndependent: RR; }; @@ -2360,10 +2366,11 @@ export type DataType = { export type Deaccession = { readonly tableName: 'Deaccession'; readonly fields: { + readonly timestampModified: string | null; readonly date1: string | null; readonly date2: string | null; - readonly deaccessionDate: string | null; readonly deaccessionNumber: string; + readonly deaccessionDate: string | null; readonly integer1: number | null; readonly integer2: number | null; readonly integer3: number | null; @@ -2382,7 +2389,6 @@ export type Deaccession = { readonly text4: string | null; readonly text5: string | null; readonly timestampCreated: string; - readonly timestampModified: string | null; readonly totalItems: number | null; readonly totalPreps: number | null; readonly type: string | null; @@ -2451,14 +2457,15 @@ export type Determination = { readonly tableName: 'Determination'; readonly fields: { readonly addendum: string | null; - readonly text1: 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; @@ -2474,6 +2481,8 @@ export type Determination = { readonly qualifier: 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; readonly text5: string | null; @@ -2482,12 +2491,9 @@ export type Determination = { readonly text8: string | null; readonly timestampCreated: string; readonly timestampModified: string | null; - readonly text2: string | null; readonly typeStatusName: string | null; readonly varQualifier: string | null; - readonly featureOrBasis: string | null; readonly version: number | null; - readonly yesNo1: boolean | null; readonly yesNo2: boolean | null; readonly yesNo3: boolean | null; readonly yesNo4: boolean | null; @@ -2499,8 +2505,8 @@ export type Determination = { readonly createdByAgent: Agent | null; readonly determiner: Agent | null; readonly modifiedByAgent: Agent | null; - readonly taxon: Taxon | null; readonly preferredTaxon: Taxon | null; + readonly taxon: Taxon | null; }; readonly toManyDependent: { readonly determinationCitations: RA; @@ -3339,8 +3345,8 @@ export type Gift = { readonly srcTaxonomy: string | null; readonly specialConditions: string | null; readonly status: string | null; - readonly text1: string | null; readonly text2: string | null; + readonly text1: string | null; readonly text3: string | null; readonly text4: string | null; readonly text5: string | null; @@ -3709,6 +3715,7 @@ 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; @@ -3730,7 +3737,6 @@ 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; @@ -3856,11 +3862,8 @@ export type LoanReturnPreparation = { export type Locality = { readonly tableName: 'Locality'; readonly fields: { - readonly text1: string | null; - readonly text2: 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; @@ -3885,6 +3888,8 @@ 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; @@ -3896,6 +3901,7 @@ export type Locality = { 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; @@ -4390,12 +4396,10 @@ export type Preparation = { readonly sampleNumber: string | null; readonly status: string | null; readonly storageLocation: string | null; - readonly text1: string | null; readonly text10: string | null; 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; @@ -4405,8 +4409,10 @@ export type Preparation = { readonly text9: string | null; readonly timestampCreated: string; readonly timestampModified: string | null; - readonly version: number | null; + readonly text1: string | null; readonly yesNo1: boolean | null; + readonly version: number | null; + readonly text2: string | null; readonly yesNo2: boolean | null; readonly yesNo3: boolean | null; }; @@ -4783,7 +4789,10 @@ 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,8 +4803,6 @@ 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; @@ -4804,7 +4811,6 @@ 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; }; @@ -4902,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; @@ -5614,59 +5620,59 @@ export type StorageTreeDefItem = { export type Taxon = { readonly tableName: 'Taxon'; readonly fields: { - readonly text10: string | null; readonly author: string | null; readonly citesStatus: string | null; - readonly name: string; - readonly text3: string | null; - readonly text5: string | null; readonly colStatus: string | null; readonly commonName: string | null; - readonly text13: string | null; readonly cultivarName: string | null; - readonly environmentalProtectionStatus: string | null; readonly esaStatus: string | null; - readonly text14: string | null; readonly fullName: string | null; readonly groupNumber: string | null; readonly guid: string | null; readonly highestChildNodeNumber: number | null; - readonly text12: string | null; - readonly text11: string | null; readonly integer1: number | null; readonly integer2: number | null; readonly integer3: number | null; readonly integer4: number | null; readonly integer5: number | null; readonly isHybrid: boolean; - readonly text8: string | null; readonly isAccepted: boolean; readonly isisNumber: string | null; readonly labelFormat: string | null; readonly lsid: string | null; - readonly text4: string | null; + readonly name: string; readonly ncbiTaxonNumber: string | null; - readonly text7: string | null; readonly nodeNumber: number | null; readonly number1: number | null; readonly number2: number | null; readonly number3: number | null; readonly number4: number | null; readonly number5: number | null; + readonly environmentalProtectionStatus: string | null; readonly rankId: number; readonly remarks: string | null; - readonly text6: string | null; - readonly text15: string | null; - readonly text9: string | null; readonly source: string | null; readonly taxonomicSerialNumber: string | null; readonly text1: string | null; readonly text2: 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 text20: 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 unitInd1: string | null; diff --git a/specifyweb/specify/api.py b/specifyweb/specify/api.py index 2980b4945ff..854b8d258a3 100644 --- a/specifyweb/specify/api.py +++ b/specifyweb/specify/api.py @@ -915,7 +915,16 @@ def field_to_val(obj, field, checker: ReadPermChecker) -> Any: related_obj = getattr(obj, field.name, None) if related_obj is None: return None return _obj_to_data(related_obj, checker) - related_id = getattr(obj, field.name + '_id') + + # The FK can exist on the other side in the case of one_to_one + # relationships + has_fk = hasattr(obj, field.name + '_id') + if has_fk: + related_id = getattr(obj, field.name + '_id') + else: + related_obj = getattr(obj, field.name, None) + related_id = getattr(related_obj, 'id', None) + if related_id is None: return None return uri_for_model(field.related_model, related_id) else: diff --git a/specifyweb/specify/datamodel.py b/specifyweb/specify/datamodel.py index f08d922ec30..5adb643be89 100644 --- a/specifyweb/specify/datamodel.py +++ b/specifyweb/specify/datamodel.py @@ -1594,7 +1594,7 @@ Relationship(name='visibilitySetBy', type='many-to-one',required=False, relatedModelName='SpecifyUser', column='VisibilitySetByID'), Relationship(name='voucherRelationships', type='one-to-many',required=False, relatedModelName='VoucherRelationship', otherSideName='collectionObject', dependent=True), Relationship(name='collectionObjectType', type='many-to-one', required=True, relatedModelName='CollectionObjectType', column='CollectionObjectTypeID'), - Relationship(name='cojo', type='one-to-one', required=False, relatedModelName='CollectionObjectGroupJoin', otherSideName='childco', dependent=True), + Relationship(name='cojo', type='one-to-one', required=False, relatedModelName='CollectionObjectGroupJoin', otherSideName='childco'), Relationship(name='absoluteAges', type='one-to-many', required=False, relatedModelName='AbsoluteAge', otherSideName='collectionObject', dependent=True), Relationship(name='relativeAges', type='one-to-many', required=False, relatedModelName='RelativeAge', otherSideName='collectionObject', dependent=True), ], @@ -8298,7 +8298,7 @@ relationships=[ Relationship(name='collection', type='many-to-one', required=False, relatedModelName='Collection', column='CollectionID'), Relationship(name='cogType', type='many-to-one', required=True, relatedModelName='CollectionObjectGroupType', column='COGTypeID'), - Relationship(name='cojo', type='one-to-one', required=False, relatedModelName='CollectionObjectGroupJoin',otherSideName='childCog', dependent=True), + Relationship(name='cojo', type='one-to-one', required=False, relatedModelName='CollectionObjectGroupJoin',otherSideName='childCog'), Relationship(name='children', type='one-to-many', required=False, dependent=True, relatedModelName='CollectionObjectGroupJoin', otherSideName='parentCog'), 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'), From d29e573c3b2740cf071b4ce9ca600efcfb1c17da Mon Sep 17 00:00:00 2001 From: melton-jason Date: Mon, 30 Dec 2024 09:12:20 -0600 Subject: [PATCH 02/11] Listen to one-to-one changes in independent collections --- .../js_src/lib/components/DataModel/collectionApi.ts | 5 ++++- .../frontend/js_src/lib/components/DataModel/resourceApi.ts | 5 ----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/specifyweb/frontend/js_src/lib/components/DataModel/collectionApi.ts b/specifyweb/frontend/js_src/lib/components/DataModel/collectionApi.ts index 0d7e8925031..459988bac97 100644 --- a/specifyweb/frontend/js_src/lib/components/DataModel/collectionApi.ts +++ b/specifyweb/frontend/js_src/lib/components/DataModel/collectionApi.ts @@ -201,7 +201,10 @@ export const IndependentCollection = LazyCollection.extend({ 'change', function (resource: SpecifyResource) { if (resource.isBeingInitialized()) return; - if (relationshipIsToMany(this.field)) { + if ( + relationshipIsToMany(this.field) || + this.field.type === 'one-to-one' + ) { const otherSideName = this.field.getReverse().name; this.related.set(otherSideName, resource); } diff --git a/specifyweb/frontend/js_src/lib/components/DataModel/resourceApi.ts b/specifyweb/frontend/js_src/lib/components/DataModel/resourceApi.ts index 5d049add063..df445dd4358 100644 --- a/specifyweb/frontend/js_src/lib/components/DataModel/resourceApi.ts +++ b/specifyweb/frontend/js_src/lib/components/DataModel/resourceApi.ts @@ -47,11 +47,6 @@ function eventHandlerForToOne(related, field) { this.set(field.name, related.url()); return; } - case 'changed': { - if (!related.isBeingInitialized()) { - this.set(field.name, related); - } - } } // Pass change:field events up the tree, updating fields with dot notation From 7351a223275ebe68d5695e2a782b6e7e060f8992 Mon Sep 17 00:00:00 2001 From: melton-jason Date: Thu, 2 Jan 2025 10:07:26 -0600 Subject: [PATCH 03/11] Resync frontend/backend datamodel, add remove button to COJO subview for CO and COG --- .../FormSliders/IntegratedRecordSelector.tsx | 10 +--------- specifyweb/specify/datamodel.py | 4 ++-- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/specifyweb/frontend/js_src/lib/components/FormSliders/IntegratedRecordSelector.tsx b/specifyweb/frontend/js_src/lib/components/FormSliders/IntegratedRecordSelector.tsx index 13ea584448e..30cff8f1029 100644 --- a/specifyweb/frontend/js_src/lib/components/FormSliders/IntegratedRecordSelector.tsx +++ b/specifyweb/frontend/js_src/lib/components/FormSliders/IntegratedRecordSelector.tsx @@ -163,12 +163,6 @@ export function IntegratedRecordSelector({ const isTaxonTreeDefItemTable = collection.table.specifyTable.name === 'TaxonTreeDefItem'; - const isCOJOFull = - relationship.relatedTable.name === 'CollectionObjectGroupJoin' - ? typeof collection.models[0] === 'object' && - typeof collection.models[0].get('parentCog') === 'string' - : false; - const isLoanPrep = relationship.relatedTable.name === 'LoanPreparation'; const disableRemove = isLoanPrep && @@ -311,9 +305,7 @@ export function IntegratedRecordSelector({ {hasTablePermission( relationship.relatedTable.name, isDependent ? 'delete' : 'read' - ) && - typeof handleRemove === 'function' && - !isCOJOFull ? ( + ) && typeof handleRemove === 'function' ? ( Date: Mon, 6 Jan 2025 10:42:19 -0600 Subject: [PATCH 04/11] Always strictly get datamodel table from models.py --- specifyweb/specify/models.py | 396 +++++++++++++++++------------------ 1 file changed, 198 insertions(+), 198 deletions(-) diff --git a/specifyweb/specify/models.py b/specifyweb/specify/models.py index 6cea139f3d5..69a13b2cfdc 100644 --- a/specifyweb/specify/models.py +++ b/specifyweb/specify/models.py @@ -29,7 +29,7 @@ def custom_save(self, *args, **kwargs): # The original models in this file are based on the Specify 6.8.03 datamodel schema. class Accession(models.Model): - specify_model = datamodel.get_table('accession') + specify_model = datamodel.get_table_strict('accession') # ID Field id = models.AutoField(primary_key=True, db_column='accessionid') @@ -80,7 +80,7 @@ class Meta: save = partialmethod(custom_save) class Accessionagent(models.Model): - specify_model = datamodel.get_table('accessionagent') + specify_model = datamodel.get_table_strict('accessionagent') # ID Field id = models.AutoField(primary_key=True, db_column='accessionagentid') @@ -108,7 +108,7 @@ class Meta: save = partialmethod(custom_save) class Accessionattachment(models.Model): - specify_model = datamodel.get_table('accessionattachment') + specify_model = datamodel.get_table_strict('accessionattachment') # ID Field id = models.AutoField(primary_key=True, db_column='accessionattachmentid') @@ -134,7 +134,7 @@ class Meta: save = partialmethod(custom_save) class Accessionauthorization(models.Model): - specify_model = datamodel.get_table('accessionauthorization') + specify_model = datamodel.get_table_strict('accessionauthorization') # ID Field id = models.AutoField(primary_key=True, db_column='accessionauthorizationid') @@ -160,7 +160,7 @@ class Meta: save = partialmethod(custom_save) class Accessioncitation(models.Model): - specify_model = datamodel.get_table('accessioncitation') + specify_model = datamodel.get_table_strict('accessioncitation') # ID Field id = models.AutoField(primary_key=True, db_column='accessioncitationid') @@ -189,7 +189,7 @@ class Meta: save = partialmethod(custom_save) class Address(models.Model): - specify_model = datamodel.get_table('address') + specify_model = datamodel.get_table_strict('address') # ID Field id = models.AutoField(primary_key=True, db_column='addressid') @@ -234,7 +234,7 @@ class Meta: save = partialmethod(custom_save) class Addressofrecord(models.Model): - specify_model = datamodel.get_table('addressofrecord') + specify_model = datamodel.get_table_strict('addressofrecord') # ID Field id = models.AutoField(primary_key=True, db_column='addressofrecordid') @@ -264,7 +264,7 @@ class Meta: save = partialmethod(custom_save) class Agent(models.Model): - specify_model = datamodel.get_table('agent') + specify_model = datamodel.get_table_strict('agent') # ID Field id = models.AutoField(primary_key=True, db_column='agentid') @@ -332,7 +332,7 @@ class Meta: save = partialmethod(custom_save) class Agentattachment(models.Model): - specify_model = datamodel.get_table('agentattachment') + specify_model = datamodel.get_table_strict('agentattachment') # ID Field id = models.AutoField(primary_key=True, db_column='agentattachmentid') @@ -358,7 +358,7 @@ class Meta: save = partialmethod(custom_save) class Agentgeography(models.Model): - specify_model = datamodel.get_table('agentgeography') + specify_model = datamodel.get_table_strict('agentgeography') # ID Field id = models.AutoField(primary_key=True, db_column='agentgeographyid') @@ -384,7 +384,7 @@ class Meta: save = partialmethod(custom_save) class Agentidentifier(models.Model): - specify_model = datamodel.get_table('agentidentifier') + specify_model = datamodel.get_table_strict('agentidentifier') # ID Field id = models.AutoField(primary_key=True, db_column='agentidentifierid') @@ -424,7 +424,7 @@ class Meta: save = partialmethod(custom_save) class Agentspecialty(models.Model): - specify_model = datamodel.get_table('agentspecialty') + specify_model = datamodel.get_table_strict('agentspecialty') # ID Field id = models.AutoField(primary_key=True, db_column='agentspecialtyid') @@ -450,7 +450,7 @@ class Meta: save = partialmethod(custom_save) class Agentvariant(models.Model): - specify_model = datamodel.get_table('agentvariant') + specify_model = datamodel.get_table_strict('agentvariant') # ID Field id = models.AutoField(primary_key=True, db_column='agentvariantid') @@ -478,7 +478,7 @@ class Meta: save = partialmethod(custom_save) class Appraisal(models.Model): - specify_model = datamodel.get_table('appraisal') + specify_model = datamodel.get_table_strict('appraisal') # ID Field id = models.AutoField(primary_key=True, db_column='appraisalid') @@ -511,7 +511,7 @@ class Meta: save = partialmethod(custom_save) class Attachment(models.Model): - specify_model = datamodel.get_table('attachment') + specify_model = datamodel.get_table_strict('attachment') # ID Field id = models.AutoField(primary_key=True, db_column='attachmentid') @@ -567,7 +567,7 @@ class Meta: save = partialmethod(custom_save) class Attachmentimageattribute(models.Model): - specify_model = datamodel.get_table('attachmentimageattribute') + specify_model = datamodel.get_table_strict('attachmentimageattribute') # ID Field id = models.AutoField(primary_key=True, db_column='attachmentimageattributeid') @@ -607,7 +607,7 @@ class Meta: save = partialmethod(custom_save) class Attachmentmetadata(models.Model): - specify_model = datamodel.get_table('attachmentmetadata') + specify_model = datamodel.get_table_strict('attachmentmetadata') # ID Field id = models.AutoField(primary_key=True, db_column='attachmentmetadataid') @@ -632,7 +632,7 @@ class Meta: save = partialmethod(custom_save) class Attachmenttag(models.Model): - specify_model = datamodel.get_table('attachmenttag') + specify_model = datamodel.get_table_strict('attachmenttag') # ID Field id = models.AutoField(primary_key=True, db_column='attachmenttagid') @@ -656,7 +656,7 @@ class Meta: save = partialmethod(custom_save) class Attributedef(models.Model): - specify_model = datamodel.get_table('attributedef') + specify_model = datamodel.get_table_strict('attributedef') # ID Field id = models.AutoField(primary_key=True, db_column='attributedefid') @@ -683,7 +683,7 @@ class Meta: save = partialmethod(custom_save) class Author(models.Model): - specify_model = datamodel.get_table('author') + specify_model = datamodel.get_table_strict('author') # ID Field id = models.AutoField(primary_key=True, db_column='authorid') @@ -710,7 +710,7 @@ class Meta: save = partialmethod(custom_save) class Autonumberingscheme(models.Model): - specify_model = datamodel.get_table('autonumberingscheme') + specify_model = datamodel.get_table_strict('autonumberingscheme') # ID Field id = models.AutoField(primary_key=True, db_column='autonumberingschemeid') @@ -740,7 +740,7 @@ class Meta: save = partialmethod(custom_save) class Borrow(models.Model): - specify_model = datamodel.get_table('borrow') + specify_model = datamodel.get_table_strict('borrow') # ID Field id = models.AutoField(primary_key=True, db_column='borrowid') @@ -787,7 +787,7 @@ class Meta: save = partialmethod(custom_save) class Borrowagent(models.Model): - specify_model = datamodel.get_table('borrowagent') + specify_model = datamodel.get_table_strict('borrowagent') # ID Field id = models.AutoField(primary_key=True, db_column='borrowagentid') @@ -818,7 +818,7 @@ class Meta: save = partialmethod(custom_save) class Borrowattachment(models.Model): - specify_model = datamodel.get_table('borrowattachment') + specify_model = datamodel.get_table_strict('borrowattachment') # ID Field id = models.AutoField(primary_key=True, db_column='borrowattachmentid') @@ -844,7 +844,7 @@ class Meta: save = partialmethod(custom_save) class Borrowmaterial(models.Model): - specify_model = datamodel.get_table('borrowmaterial') + specify_model = datamodel.get_table_strict('borrowmaterial') # ID Field id = models.AutoField(primary_key=True, db_column='borrowmaterialid') @@ -882,7 +882,7 @@ class Meta: save = partialmethod(custom_save) class Borrowreturnmaterial(models.Model): - specify_model = datamodel.get_table('borrowreturnmaterial') + specify_model = datamodel.get_table_strict('borrowreturnmaterial') # ID Field id = models.AutoField(primary_key=True, db_column='borrowreturnmaterialid') @@ -914,7 +914,7 @@ class Meta: save = partialmethod(custom_save) class Collectingevent(models.Model): - specify_model = datamodel.get_table('collectingevent') + specify_model = datamodel.get_table_strict('collectingevent') # ID Field id = models.AutoField(primary_key=True, db_column='collectingeventid') @@ -984,7 +984,7 @@ class Meta: save = partialmethod(custom_save) class Collectingeventattachment(models.Model): - specify_model = datamodel.get_table('collectingeventattachment') + specify_model = datamodel.get_table_strict('collectingeventattachment') # ID Field id = models.AutoField(primary_key=True, db_column='collectingeventattachmentid') @@ -1014,7 +1014,7 @@ class Meta: save = partialmethod(custom_save) class Collectingeventattr(models.Model): - specify_model = datamodel.get_table('collectingeventattr') + specify_model = datamodel.get_table_strict('collectingeventattr') # ID Field id = models.AutoField(primary_key=True, db_column='attrid') @@ -1044,7 +1044,7 @@ class Meta: save = partialmethod(custom_save) class Collectingeventattribute(models.Model): - specify_model = datamodel.get_table('collectingeventattribute') + specify_model = datamodel.get_table_strict('collectingeventattribute') # ID Field id = models.AutoField(primary_key=True, db_column='collectingeventattributeid') @@ -1117,7 +1117,7 @@ class Meta: save = partialmethod(custom_save) class Collectingeventauthorization(models.Model): - specify_model = datamodel.get_table('collectingeventauthorization') + specify_model = datamodel.get_table_strict('collectingeventauthorization') # ID Field id = models.AutoField(primary_key=True, db_column='collectingeventauthorizationid') @@ -1142,7 +1142,7 @@ class Meta: save = partialmethod(custom_save) class Collectingtrip(models.Model): - specify_model = datamodel.get_table('collectingtrip') + specify_model = datamodel.get_table_strict('collectingtrip') # ID Field id = models.AutoField(primary_key=True, db_column='collectingtripid') @@ -1203,7 +1203,7 @@ class Meta: save = partialmethod(custom_save) class Collectingtripattachment(models.Model): - specify_model = datamodel.get_table('collectingtripattachment') + specify_model = datamodel.get_table_strict('collectingtripattachment') # ID Field id = models.AutoField(primary_key=True, db_column='collectingtripattachmentid') @@ -1233,7 +1233,7 @@ class Meta: save = partialmethod(custom_save) class Collectingtripattribute(models.Model): - specify_model = datamodel.get_table('collectingtripattribute') + specify_model = datamodel.get_table_strict('collectingtripattribute') # ID Field id = models.AutoField(primary_key=True, db_column='collectingtripattributeid') @@ -1305,7 +1305,7 @@ class Meta: save = partialmethod(custom_save) class Collectingtripauthorization(models.Model): - specify_model = datamodel.get_table('collectingtripauthorization') + specify_model = datamodel.get_table_strict('collectingtripauthorization') # ID Field id = models.AutoField(primary_key=True, db_column='collectingtripauthorizationid') @@ -1330,7 +1330,7 @@ class Meta: save = partialmethod(custom_save) class Collection(models.Model): - specify_model = datamodel.get_table('collection') + specify_model = datamodel.get_table_strict('collection') # ID Field id = models.AutoField(primary_key=True, db_column='usergroupscopeid') @@ -1381,7 +1381,7 @@ class Meta: save = partialmethod(custom_save) class Collectionobject(models.Model): - specify_model = datamodel.get_table('collectionobject') + specify_model = datamodel.get_table_strict('collectionobject') # ID Field id = models.AutoField(primary_key=True, db_column='collectionobjectid') @@ -1485,7 +1485,7 @@ class Meta: save = partialmethod(custom_save) class Collectionobjectattachment(models.Model): - specify_model = datamodel.get_table('collectionobjectattachment') + specify_model = datamodel.get_table_strict('collectionobjectattachment') # ID Field id = models.AutoField(primary_key=True, db_column='collectionobjectattachmentid') @@ -1515,7 +1515,7 @@ class Meta: save = partialmethod(custom_save) class Collectionobjectattr(models.Model): - specify_model = datamodel.get_table('collectionobjectattr') + specify_model = datamodel.get_table_strict('collectionobjectattr') # ID Field id = models.AutoField(primary_key=True, db_column='attrid') @@ -1545,7 +1545,7 @@ class Meta: save = partialmethod(custom_save) class Collectionobjectattribute(models.Model): - specify_model = datamodel.get_table('collectionobjectattribute') + specify_model = datamodel.get_table_strict('collectionobjectattribute') # ID Field id = models.AutoField(primary_key=True, db_column='collectionobjectattributeid') @@ -1692,7 +1692,7 @@ class Meta: save = partialmethod(custom_save) class Collectionobjectcitation(models.Model): - specify_model = datamodel.get_table('collectionobjectcitation') + specify_model = datamodel.get_table_strict('collectionobjectcitation') # ID Field id = models.AutoField(primary_key=True, db_column='collectionobjectcitationid') @@ -1727,7 +1727,7 @@ class Meta: save = partialmethod(custom_save) class Collectionobjectproperty(models.Model): - specify_model = datamodel.get_table('collectionobjectproperty') + specify_model = datamodel.get_table_strict('collectionobjectproperty') # ID Field id = models.AutoField(primary_key=True, db_column='collectionobjectpropertyid') @@ -1916,7 +1916,7 @@ class Meta: save = partialmethod(custom_save) class Collectionreltype(models.Model): - specify_model = datamodel.get_table('collectionreltype') + specify_model = datamodel.get_table_strict('collectionreltype') # ID Field id = models.AutoField(primary_key=True, db_column='collectionreltypeid') @@ -1942,7 +1942,7 @@ class Meta: save = partialmethod(custom_save) class Collectionrelationship(models.Model): - specify_model = datamodel.get_table('collectionrelationship') + specify_model = datamodel.get_table_strict('collectionrelationship') # ID Field id = models.AutoField(primary_key=True, db_column='collectionrelationshipid') @@ -1969,7 +1969,7 @@ class Meta: save = partialmethod(custom_save) class Collector(models.Model): - specify_model = datamodel.get_table('collector') + specify_model = datamodel.get_table_strict('collector') # ID Field id = models.AutoField(primary_key=True, db_column='collectorid') @@ -2005,7 +2005,7 @@ class Meta: save = partialmethod(custom_save) class Commonnametx(models.Model): - specify_model = datamodel.get_table('commonnametx') + specify_model = datamodel.get_table_strict('commonnametx') # ID Field id = models.AutoField(primary_key=True, db_column='commonnametxid') @@ -2037,7 +2037,7 @@ class Meta: save = partialmethod(custom_save) class Commonnametxcitation(models.Model): - specify_model = datamodel.get_table('commonnametxcitation') + specify_model = datamodel.get_table_strict('commonnametxcitation') # ID Field id = models.AutoField(primary_key=True, db_column='commonnametxcitationid') @@ -2072,7 +2072,7 @@ class Meta: save = partialmethod(custom_save) class Conservdescription(models.Model): - specify_model = datamodel.get_table('conservdescription') + specify_model = datamodel.get_table_strict('conservdescription') # ID Field id = models.AutoField(primary_key=True, db_column='conservdescriptionid') @@ -2144,7 +2144,7 @@ class Meta: save = partialmethod(custom_save) class Conservdescriptionattachment(models.Model): - specify_model = datamodel.get_table('conservdescriptionattachment') + specify_model = datamodel.get_table_strict('conservdescriptionattachment') # ID Field id = models.AutoField(primary_key=True, db_column='conservdescriptionattachmentid') @@ -2170,7 +2170,7 @@ class Meta: save = partialmethod(custom_save) class Conservevent(models.Model): - specify_model = datamodel.get_table('conservevent') + specify_model = datamodel.get_table_strict('conservevent') # ID Field id = models.AutoField(primary_key=True, db_column='conserveventid') @@ -2221,7 +2221,7 @@ class Meta: save = partialmethod(custom_save) class Conserveventattachment(models.Model): - specify_model = datamodel.get_table('conserveventattachment') + specify_model = datamodel.get_table_strict('conserveventattachment') # ID Field id = models.AutoField(primary_key=True, db_column='conserveventattachmentid') @@ -2247,7 +2247,7 @@ class Meta: save = partialmethod(custom_save) class Container(models.Model): - specify_model = datamodel.get_table('container') + specify_model = datamodel.get_table_strict('container') # ID Field id = models.AutoField(primary_key=True, db_column='containerid') @@ -2280,7 +2280,7 @@ class Meta: save = partialmethod(custom_save) class Dnaprimer(models.Model): - specify_model = datamodel.get_table('dnaprimer') + specify_model = datamodel.get_table_strict('dnaprimer') # ID Field id = models.AutoField(primary_key=True, db_column='dnaprimerid') @@ -2330,7 +2330,7 @@ class Meta: save = partialmethod(custom_save) class Dnasequence(models.Model): - specify_model = datamodel.get_table('dnasequence') + specify_model = datamodel.get_table_strict('dnasequence') # ID Field id = models.AutoField(primary_key=True, db_column='dnasequenceid') @@ -2390,7 +2390,7 @@ class Meta: save = partialmethod(custom_save) class Dnasequenceattachment(models.Model): - specify_model = datamodel.get_table('dnasequenceattachment') + specify_model = datamodel.get_table_strict('dnasequenceattachment') # ID Field id = models.AutoField(primary_key=True, db_column='dnasequenceattachmentid') @@ -2416,7 +2416,7 @@ class Meta: save = partialmethod(custom_save) class Dnasequencingrun(models.Model): - specify_model = datamodel.get_table('dnasequencingrun') + specify_model = datamodel.get_table_strict('dnasequencingrun') # ID Field id = models.AutoField(primary_key=True, db_column='dnasequencingrunid') @@ -2472,7 +2472,7 @@ class Meta: save = partialmethod(custom_save) class Dnasequencingrunattachment(models.Model): - specify_model = datamodel.get_table('dnasequencingrunattachment') + specify_model = datamodel.get_table_strict('dnasequencingrunattachment') # ID Field id = models.AutoField(primary_key=True, db_column='dnasequencingrunattachmentid') @@ -2498,7 +2498,7 @@ class Meta: save = partialmethod(custom_save) class Dnasequencingruncitation(models.Model): - specify_model = datamodel.get_table('dnasequencingruncitation') + specify_model = datamodel.get_table_strict('dnasequencingruncitation') # ID Field id = models.AutoField(primary_key=True, db_column='dnasequencingruncitationid') @@ -2533,7 +2533,7 @@ class Meta: save = partialmethod(custom_save) class Datatype(models.Model): - specify_model = datamodel.get_table('datatype') + specify_model = datamodel.get_table_strict('datatype') # ID Field id = models.AutoField(primary_key=True, db_column='datatypeid') @@ -2556,7 +2556,7 @@ class Meta: save = partialmethod(custom_save) class Deaccession(models.Model): - specify_model = datamodel.get_table('deaccession') + specify_model = datamodel.get_table_strict('deaccession') # ID Field id = models.AutoField(primary_key=True, db_column='deaccessionid') @@ -2611,7 +2611,7 @@ class Meta: save = partialmethod(custom_save) class Deaccessionagent(models.Model): - specify_model = datamodel.get_table('deaccessionagent') + specify_model = datamodel.get_table_strict('deaccessionagent') # ID Field id = models.AutoField(primary_key=True, db_column='deaccessionagentid') @@ -2638,7 +2638,7 @@ class Meta: save = partialmethod(custom_save) class Deaccessionattachment(models.Model): - specify_model = datamodel.get_table('deaccessionattachment') + specify_model = datamodel.get_table_strict('deaccessionattachment') # ID Field id = models.AutoField(primary_key=True, db_column='deaccessionattachmentid') @@ -2664,7 +2664,7 @@ class Meta: save = partialmethod(custom_save) class Determination(models.Model): - specify_model = datamodel.get_table('determination') + specify_model = datamodel.get_table_strict('determination') # ID Field id = models.AutoField(primary_key=True, db_column='determinationid') @@ -2736,7 +2736,7 @@ class Meta: save = partialmethod(custom_save) class Determinationcitation(models.Model): - specify_model = datamodel.get_table('determinationcitation') + specify_model = datamodel.get_table_strict('determinationcitation') # ID Field id = models.AutoField(primary_key=True, db_column='determinationcitationid') @@ -2770,7 +2770,7 @@ class Meta: save = partialmethod(custom_save) class Determiner(models.Model): - specify_model = datamodel.get_table('determiner') + specify_model = datamodel.get_table_strict('determiner') # ID Field id = models.AutoField(primary_key=True, db_column='determinerid') @@ -2802,7 +2802,7 @@ class Meta: save = partialmethod(custom_save) class Discipline(model_extras.Discipline): - specify_model = datamodel.get_table('discipline') + specify_model = datamodel.get_table_strict('discipline') # ID Field id = models.AutoField(primary_key=True, db_column='usergroupscopeid') @@ -2839,7 +2839,7 @@ class Meta: save = partialmethod(custom_save) class Disposal(models.Model): - specify_model = datamodel.get_table('disposal') + specify_model = datamodel.get_table_strict('disposal') # ID Field id = models.AutoField(primary_key=True, db_column='disposalid') @@ -2877,7 +2877,7 @@ class Meta: save = partialmethod(custom_save) class Disposalagent(models.Model): - specify_model = datamodel.get_table('disposalagent') + specify_model = datamodel.get_table_strict('disposalagent') # ID Field id = models.AutoField(primary_key=True, db_column='disposalagentid') @@ -2904,7 +2904,7 @@ class Meta: save = partialmethod(custom_save) class Disposalattachment(models.Model): - specify_model = datamodel.get_table('disposalattachment') + specify_model = datamodel.get_table_strict('disposalattachment') # ID Field id = models.AutoField(primary_key=True, db_column='disposalattachmentid') @@ -2930,7 +2930,7 @@ class Meta: save = partialmethod(custom_save) class Disposalpreparation(models.Model): - specify_model = datamodel.get_table('disposalpreparation') + specify_model = datamodel.get_table_strict('disposalpreparation') # ID Field id = models.AutoField(primary_key=True, db_column='disposalpreparationid') @@ -2957,7 +2957,7 @@ class Meta: save = partialmethod(custom_save) class Division(models.Model): - specify_model = datamodel.get_table('division') + specify_model = datamodel.get_table_strict('division') # ID Field id = models.AutoField(primary_key=True, db_column='usergroupscopeid') @@ -2993,7 +2993,7 @@ class Meta: save = partialmethod(custom_save) class Exchangein(models.Model): - specify_model = datamodel.get_table('exchangein') + specify_model = datamodel.get_table_strict('exchangein') # ID Field id = models.AutoField(primary_key=True, db_column='exchangeinid') @@ -3037,7 +3037,7 @@ class Meta: save = partialmethod(custom_save) class Exchangeinattachment(models.Model): - specify_model = datamodel.get_table('exchangeinattachment') + specify_model = datamodel.get_table_strict('exchangeinattachment') # ID Field id = models.AutoField(primary_key=True, db_column='exchangeinattachmentid') @@ -3063,7 +3063,7 @@ class Meta: save = partialmethod(custom_save) class Exchangeinprep(models.Model): - specify_model = datamodel.get_table('exchangeinprep') + specify_model = datamodel.get_table_strict('exchangeinprep') # ID Field id = models.AutoField(primary_key=True, db_column='exchangeinprepid') @@ -3097,7 +3097,7 @@ class Meta: save = partialmethod(custom_save) class Exchangeout(models.Model): - specify_model = datamodel.get_table('exchangeout') + specify_model = datamodel.get_table_strict('exchangeout') # ID Field id = models.AutoField(primary_key=True, db_column='exchangeoutid') @@ -3143,7 +3143,7 @@ class Meta: save = partialmethod(custom_save) class Exchangeoutattachment(models.Model): - specify_model = datamodel.get_table('exchangeoutattachment') + specify_model = datamodel.get_table_strict('exchangeoutattachment') # ID Field id = models.AutoField(primary_key=True, db_column='exchangeoutattachmentid') @@ -3169,7 +3169,7 @@ class Meta: save = partialmethod(custom_save) class Exchangeoutprep(models.Model): - specify_model = datamodel.get_table('exchangeoutprep') + specify_model = datamodel.get_table_strict('exchangeoutprep') # ID Field id = models.AutoField(primary_key=True, db_column='exchangeoutprepid') @@ -3203,7 +3203,7 @@ class Meta: save = partialmethod(custom_save) class Exsiccata(models.Model): - specify_model = datamodel.get_table('exsiccata') + specify_model = datamodel.get_table_strict('exsiccata') # ID Field id = models.AutoField(primary_key=True, db_column='exsiccataid') @@ -3229,7 +3229,7 @@ class Meta: save = partialmethod(custom_save) class Exsiccataitem(models.Model): - specify_model = datamodel.get_table('exsiccataitem') + specify_model = datamodel.get_table_strict('exsiccataitem') # ID Field id = models.AutoField(primary_key=True, db_column='exsiccataitemid') @@ -3255,7 +3255,7 @@ class Meta: save = partialmethod(custom_save) class Extractor(models.Model): - specify_model = datamodel.get_table('extractor') + specify_model = datamodel.get_table_strict('extractor') # ID Field id = models.AutoField(primary_key=True, db_column='extractorid') @@ -3286,7 +3286,7 @@ class Meta: save = partialmethod(custom_save) class Fieldnotebook(models.Model): - specify_model = datamodel.get_table('fieldnotebook') + specify_model = datamodel.get_table_strict('fieldnotebook') # ID Field id = models.AutoField(primary_key=True, db_column='fieldnotebookid') @@ -3321,7 +3321,7 @@ class Meta: save = partialmethod(custom_save) class Fieldnotebookattachment(models.Model): - specify_model = datamodel.get_table('fieldnotebookattachment') + specify_model = datamodel.get_table_strict('fieldnotebookattachment') # ID Field id = models.AutoField(primary_key=True, db_column='fieldnotebookattachmentid') @@ -3347,7 +3347,7 @@ class Meta: save = partialmethod(custom_save) class Fieldnotebookpage(models.Model): - specify_model = datamodel.get_table('fieldnotebookpage') + specify_model = datamodel.get_table_strict('fieldnotebookpage') # ID Field id = models.AutoField(primary_key=True, db_column='fieldnotebookpageid') @@ -3378,7 +3378,7 @@ class Meta: save = partialmethod(custom_save) class Fieldnotebookpageattachment(models.Model): - specify_model = datamodel.get_table('fieldnotebookpageattachment') + specify_model = datamodel.get_table_strict('fieldnotebookpageattachment') # ID Field id = models.AutoField(primary_key=True, db_column='fieldnotebookpageattachmentid') @@ -3404,7 +3404,7 @@ class Meta: save = partialmethod(custom_save) class Fieldnotebookpageset(models.Model): - specify_model = datamodel.get_table('fieldnotebookpageset') + specify_model = datamodel.get_table_strict('fieldnotebookpageset') # ID Field id = models.AutoField(primary_key=True, db_column='fieldnotebookpagesetid') @@ -3438,7 +3438,7 @@ class Meta: save = partialmethod(custom_save) class Fieldnotebookpagesetattachment(models.Model): - specify_model = datamodel.get_table('fieldnotebookpagesetattachment') + specify_model = datamodel.get_table_strict('fieldnotebookpagesetattachment') # ID Field id = models.AutoField(primary_key=True, db_column='fieldnotebookpagesetattachmentid') @@ -3464,7 +3464,7 @@ class Meta: save = partialmethod(custom_save) class Fundingagent(models.Model): - specify_model = datamodel.get_table('fundingagent') + specify_model = datamodel.get_table_strict('fundingagent') # ID Field id = models.AutoField(primary_key=True, db_column='fundingagentid') @@ -3497,7 +3497,7 @@ class Meta: save = partialmethod(custom_save) class Geocoorddetail(models.Model): - specify_model = datamodel.get_table('geocoorddetail') + specify_model = datamodel.get_table_strict('geocoorddetail') # ID Field id = models.AutoField(primary_key=True, db_column='geocoorddetailid') @@ -3559,7 +3559,7 @@ class Meta: save = partialmethod(custom_save) class Geography(model_extras.Geography): - specify_model = datamodel.get_table('geography') + specify_model = datamodel.get_table_strict('geography') # ID Field id = models.AutoField(primary_key=True, db_column='geographyid') @@ -3609,7 +3609,7 @@ class Meta: save = partialmethod(custom_save) class Geographytreedef(models.Model): - specify_model = datamodel.get_table('geographytreedef') + specify_model = datamodel.get_table_strict('geographytreedef') # ID Field id = models.AutoField(primary_key=True, db_column='geographytreedefid') @@ -3635,7 +3635,7 @@ class Meta: save = partialmethod(custom_save) class Geographytreedefitem(model_extras.Geographytreedefitem): - specify_model = datamodel.get_table('geographytreedefitem') + specify_model = datamodel.get_table_strict('geographytreedefitem') # ID Field id = models.AutoField(primary_key=True, db_column='geographytreedefitemid') @@ -3668,7 +3668,7 @@ class Meta: save = partialmethod(custom_save) class Geologictimeperiod(model_extras.Geologictimeperiod): - specify_model = datamodel.get_table('geologictimeperiod') # aka. Chronostratigraphy + specify_model = datamodel.get_table_strict('geologictimeperiod') # aka. Chronostratigraphy # ID Field id = models.AutoField(primary_key=True, db_column='geologictimeperiodid') @@ -3715,7 +3715,7 @@ class Meta: save = partialmethod(custom_save) class Geologictimeperiodtreedef(models.Model): - specify_model = datamodel.get_table('geologictimeperiodtreedef') + specify_model = datamodel.get_table_strict('geologictimeperiodtreedef') # ID Field id = models.AutoField(primary_key=True, db_column='geologictimeperiodtreedefid') @@ -3741,7 +3741,7 @@ class Meta: save = partialmethod(custom_save) class Geologictimeperiodtreedefitem(model_extras.Geologictimeperiodtreedefitem): - specify_model = datamodel.get_table('geologictimeperiodtreedefitem') + specify_model = datamodel.get_table_strict('geologictimeperiodtreedefitem') # ID Field id = models.AutoField(primary_key=True, db_column='geologictimeperiodtreedefitemid') @@ -3774,7 +3774,7 @@ class Meta: save = partialmethod(custom_save) class Gift(models.Model): - specify_model = datamodel.get_table('gift') + specify_model = datamodel.get_table_strict('gift') # ID Field id = models.AutoField(primary_key=True, db_column='giftid') @@ -3830,7 +3830,7 @@ class Meta: save = partialmethod(custom_save) class Giftagent(models.Model): - specify_model = datamodel.get_table('giftagent') + specify_model = datamodel.get_table_strict('giftagent') # ID Field id = models.AutoField(primary_key=True, db_column='giftagentid') @@ -3862,7 +3862,7 @@ class Meta: save = partialmethod(custom_save) class Giftattachment(models.Model): - specify_model = datamodel.get_table('giftattachment') + specify_model = datamodel.get_table_strict('giftattachment') # ID Field id = models.AutoField(primary_key=True, db_column='giftattachmentid') @@ -3888,7 +3888,7 @@ class Meta: save = partialmethod(custom_save) class Giftpreparation(models.Model): - specify_model = datamodel.get_table('giftpreparation') + specify_model = datamodel.get_table_strict('giftpreparation') # ID Field id = models.AutoField(primary_key=True, db_column='giftpreparationid') @@ -3926,7 +3926,7 @@ class Meta: save = partialmethod(custom_save) class Groupperson(models.Model): - specify_model = datamodel.get_table('groupperson') + specify_model = datamodel.get_table_strict('groupperson') # ID Field id = models.AutoField(primary_key=True, db_column='grouppersonid') @@ -3954,7 +3954,7 @@ class Meta: save = partialmethod(custom_save) class Inforequest(models.Model): - specify_model = datamodel.get_table('inforequest') + specify_model = datamodel.get_table_strict('inforequest') # ID Field id = models.AutoField(primary_key=True, db_column='inforequestid') @@ -3989,7 +3989,7 @@ class Meta: save = partialmethod(custom_save) class Institution(models.Model): - specify_model = datamodel.get_table('institution') + specify_model = datamodel.get_table_strict('institution') # ID Field id = models.AutoField(primary_key=True, db_column='usergroupscopeid') @@ -4043,7 +4043,7 @@ class Meta: save = partialmethod(custom_save) class Institutionnetwork(models.Model): - specify_model = datamodel.get_table('institutionnetwork') + specify_model = datamodel.get_table_strict('institutionnetwork') # ID Field id = models.AutoField(primary_key=True, db_column='institutionnetworkid') @@ -4081,7 +4081,7 @@ class Meta: save = partialmethod(custom_save) class Journal(models.Model): - specify_model = datamodel.get_table('journal') + specify_model = datamodel.get_table_strict('journal') # ID Field id = models.AutoField(primary_key=True, db_column='journalid') @@ -4114,7 +4114,7 @@ class Meta: save = partialmethod(custom_save) class Latlonpolygon(models.Model): - specify_model = datamodel.get_table('latlonpolygon') + specify_model = datamodel.get_table_strict('latlonpolygon') # ID Field id = models.AutoField(primary_key=True, db_column='latlonpolygonid') @@ -4141,7 +4141,7 @@ class Meta: save = partialmethod(custom_save) class Latlonpolygonpnt(models.Model): - specify_model = datamodel.get_table('latlonpolygonpnt') + specify_model = datamodel.get_table_strict('latlonpolygonpnt') # ID Field id = models.AutoField(primary_key=True, db_column='latlonpolygonpntid') @@ -4162,7 +4162,7 @@ class Meta: save = partialmethod(custom_save) class Lithostrat(model_extras.Lithostrat): - specify_model = datamodel.get_table('lithostrat') + specify_model = datamodel.get_table_strict('lithostrat') # ID Field id = models.AutoField(primary_key=True, db_column='lithostratid') @@ -4207,7 +4207,7 @@ class Meta: save = partialmethod(custom_save) class Lithostrattreedef(models.Model): - specify_model = datamodel.get_table('lithostrattreedef') + specify_model = datamodel.get_table_strict('lithostrattreedef') # ID Field id = models.AutoField(primary_key=True, db_column='lithostrattreedefid') @@ -4233,7 +4233,7 @@ class Meta: save = partialmethod(custom_save) class Lithostrattreedefitem(model_extras.Lithostrattreedefitem): - specify_model = datamodel.get_table('lithostrattreedefitem') + specify_model = datamodel.get_table_strict('lithostrattreedefitem') # ID Field id = models.AutoField(primary_key=True, db_column='lithostrattreedefitemid') @@ -4266,7 +4266,7 @@ class Meta: save = partialmethod(custom_save) class Loan(models.Model): - specify_model = datamodel.get_table('loan') + specify_model = datamodel.get_table_strict('loan') # ID Field id = models.AutoField(primary_key=True, db_column='loanid') @@ -4325,7 +4325,7 @@ class Meta: save = partialmethod(custom_save) class Loanagent(models.Model): - specify_model = datamodel.get_table('loanagent') + specify_model = datamodel.get_table_strict('loanagent') # ID Field id = models.AutoField(primary_key=True, db_column='loanagentid') @@ -4356,7 +4356,7 @@ class Meta: save = partialmethod(custom_save) class Loanattachment(models.Model): - specify_model = datamodel.get_table('loanattachment') + specify_model = datamodel.get_table_strict('loanattachment') # ID Field id = models.AutoField(primary_key=True, db_column='loanattachmentid') @@ -4382,7 +4382,7 @@ class Meta: save = partialmethod(custom_save) class Loanpreparation(models.Model): - specify_model = datamodel.get_table('loanpreparation') + specify_model = datamodel.get_table_strict('loanpreparation') # ID Field id = models.AutoField(primary_key=True, db_column='loanpreparationid') @@ -4423,7 +4423,7 @@ class Meta: save = partialmethod(custom_save) class Loanreturnpreparation(models.Model): - specify_model = datamodel.get_table('loanreturnpreparation') + specify_model = datamodel.get_table_strict('loanreturnpreparation') # ID Field id = models.AutoField(primary_key=True, db_column='loanreturnpreparationid') @@ -4456,7 +4456,7 @@ class Meta: save = partialmethod(custom_save) class Locality(models.Model): - specify_model = datamodel.get_table('locality') + specify_model = datamodel.get_table_strict('locality') # ID Field id = models.AutoField(primary_key=True, db_column='localityid') @@ -4532,7 +4532,7 @@ class Meta: save = partialmethod(custom_save) class Localityattachment(models.Model): - specify_model = datamodel.get_table('localityattachment') + specify_model = datamodel.get_table_strict('localityattachment') # ID Field id = models.AutoField(primary_key=True, db_column='localityattachmentid') @@ -4558,7 +4558,7 @@ class Meta: save = partialmethod(custom_save) class Localitycitation(models.Model): - specify_model = datamodel.get_table('localitycitation') + specify_model = datamodel.get_table_strict('localitycitation') # ID Field id = models.AutoField(primary_key=True, db_column='localitycitationid') @@ -4592,7 +4592,7 @@ class Meta: save = partialmethod(custom_save) class Localitydetail(models.Model): - specify_model = datamodel.get_table('localitydetail') + specify_model = datamodel.get_table_strict('localitydetail') # ID Field id = models.AutoField(primary_key=True, db_column='localitydetailid') @@ -4662,7 +4662,7 @@ class Meta: save = partialmethod(custom_save) class Localitynamealias(models.Model): - specify_model = datamodel.get_table('localitynamealias') + specify_model = datamodel.get_table_strict('localitynamealias') # ID Field id = models.AutoField(primary_key=True, db_column='localitynamealiasid') @@ -4691,7 +4691,7 @@ class Meta: save = partialmethod(custom_save) class Materialsample(models.Model): - specify_model = datamodel.get_table('materialsample') + specify_model = datamodel.get_table_strict('materialsample') # ID Field id = models.AutoField(primary_key=True, db_column='materialsampleid') @@ -4758,7 +4758,7 @@ class Meta: save = partialmethod(custom_save) class Morphbankview(models.Model): - specify_model = datamodel.get_table('morphbankview') + specify_model = datamodel.get_table_strict('morphbankview') # ID Field id = models.AutoField(primary_key=True, db_column='morphbankviewid') @@ -4789,7 +4789,7 @@ class Meta: save = partialmethod(custom_save) class Otheridentifier(models.Model): - specify_model = datamodel.get_table('otheridentifier') + specify_model = datamodel.get_table_strict('otheridentifier') # ID Field id = models.AutoField(primary_key=True, db_column='otheridentifierid') @@ -4835,7 +4835,7 @@ class Meta: save = partialmethod(custom_save) class Paleocontext(models.Model): - specify_model = datamodel.get_table('paleocontext') # aka. GeoContext + specify_model = datamodel.get_table_strict('paleocontext') # aka. GeoContext # ID Field id = models.AutoField(primary_key=True, db_column='paleocontextid') @@ -4884,7 +4884,7 @@ class Meta: save = partialmethod(custom_save) class Pcrperson(models.Model): - specify_model = datamodel.get_table('pcrperson') + specify_model = datamodel.get_table_strict('pcrperson') # ID Field id = models.AutoField(primary_key=True, db_column='pcrpersonid') @@ -4915,7 +4915,7 @@ class Meta: save = partialmethod(custom_save) class Permit(models.Model): - specify_model = datamodel.get_table('permit') + specify_model = datamodel.get_table_strict('permit') # ID Field id = models.AutoField(primary_key=True, db_column='permitid') @@ -4967,7 +4967,7 @@ class Meta: save = partialmethod(custom_save) class Permitattachment(models.Model): - specify_model = datamodel.get_table('permitattachment') + specify_model = datamodel.get_table_strict('permitattachment') # ID Field id = models.AutoField(primary_key=True, db_column='permitattachmentid') @@ -4993,7 +4993,7 @@ class Meta: save = partialmethod(custom_save) class Picklist(models.Model): - specify_model = datamodel.get_table('picklist') + specify_model = datamodel.get_table_strict('picklist') # ID Field id = models.AutoField(primary_key=True, db_column='picklistid') @@ -5030,7 +5030,7 @@ class Meta: save = partialmethod(custom_save) class Picklistitem(models.Model): - specify_model = datamodel.get_table('picklistitem') + specify_model = datamodel.get_table_strict('picklistitem') # ID Field id = models.AutoField(primary_key=True, db_column='picklistitemid') @@ -5056,7 +5056,7 @@ class Meta: save = partialmethod(custom_save) class Preptype(models.Model): - specify_model = datamodel.get_table('preptype') + specify_model = datamodel.get_table_strict('preptype') # ID Field id = models.AutoField(primary_key=True, db_column='preptypeid') @@ -5081,7 +5081,7 @@ class Meta: save = partialmethod(custom_save) class Preparation(model_extras.Preparation): - specify_model = datamodel.get_table('preparation') + specify_model = datamodel.get_table_strict('preparation') # ID Field id = models.AutoField(primary_key=True, db_column='preparationid') @@ -5158,7 +5158,7 @@ class Meta: save = partialmethod(custom_save) class Preparationattachment(models.Model): - specify_model = datamodel.get_table('preparationattachment') + specify_model = datamodel.get_table_strict('preparationattachment') # ID Field id = models.AutoField(primary_key=True, db_column='preparationattachmentid') @@ -5188,7 +5188,7 @@ class Meta: save = partialmethod(custom_save) class Preparationattr(models.Model): - specify_model = datamodel.get_table('preparationattr') + specify_model = datamodel.get_table_strict('preparationattr') # ID Field id = models.AutoField(primary_key=True, db_column='attrid') @@ -5218,7 +5218,7 @@ class Meta: save = partialmethod(custom_save) class Preparationattribute(models.Model): - specify_model = datamodel.get_table('preparationattribute') + specify_model = datamodel.get_table_strict('preparationattribute') # ID Field id = models.AutoField(primary_key=True, db_column='preparationattributeid') @@ -5285,7 +5285,7 @@ class Meta: save = partialmethod(custom_save) class Preparationproperty(models.Model): - specify_model = datamodel.get_table('preparationproperty') + specify_model = datamodel.get_table_strict('preparationproperty') # ID Field id = models.AutoField(primary_key=True, db_column='preparationpropertyid') @@ -5474,7 +5474,7 @@ class Meta: save = partialmethod(custom_save) class Project(models.Model): - specify_model = datamodel.get_table('project') + specify_model = datamodel.get_table_strict('project') # ID Field id = models.AutoField(primary_key=True, db_column='projectid') @@ -5517,7 +5517,7 @@ class Meta: save = partialmethod(custom_save) class Recordset(models.Model): - specify_model = datamodel.get_table('recordset') + specify_model = datamodel.get_table_strict('recordset') # ID Field id = models.AutoField(primary_key=True, db_column='recordsetid') @@ -5553,7 +5553,7 @@ class Meta: save = partialmethod(custom_save) class Recordsetitem(models.Model): - specify_model = datamodel.get_table('recordsetitem') + specify_model = datamodel.get_table_strict('recordsetitem') # ID Field id = models.AutoField(primary_key=True, db_column='recordsetitemid') @@ -5572,7 +5572,7 @@ class Meta: save = partialmethod(custom_save) class Referencework(models.Model): - specify_model = datamodel.get_table('referencework') + specify_model = datamodel.get_table_strict('referencework') # ID Field id = models.AutoField(primary_key=True, db_column='referenceworkid') @@ -5624,7 +5624,7 @@ class Meta: save = partialmethod(custom_save) class Referenceworkattachment(models.Model): - specify_model = datamodel.get_table('referenceworkattachment') + specify_model = datamodel.get_table_strict('referenceworkattachment') # ID Field id = models.AutoField(primary_key=True, db_column='referenceworkattachmentid') @@ -5650,7 +5650,7 @@ class Meta: save = partialmethod(custom_save) class Repositoryagreement(models.Model): - specify_model = datamodel.get_table('repositoryagreement') + specify_model = datamodel.get_table_strict('repositoryagreement') # ID Field id = models.AutoField(primary_key=True, db_column='repositoryagreementid') @@ -5692,7 +5692,7 @@ class Meta: save = partialmethod(custom_save) class Repositoryagreementattachment(models.Model): - specify_model = datamodel.get_table('repositoryagreementattachment') + specify_model = datamodel.get_table_strict('repositoryagreementattachment') # ID Field id = models.AutoField(primary_key=True, db_column='repositoryagreementattachmentid') @@ -5718,7 +5718,7 @@ class Meta: save = partialmethod(custom_save) class Shipment(models.Model): - specify_model = datamodel.get_table('shipment') + specify_model = datamodel.get_table_strict('shipment') # ID Field id = models.AutoField(primary_key=True, db_column='shipmentid') @@ -5767,7 +5767,7 @@ class Meta: save = partialmethod(custom_save) class Spappresource(models.Model): - specify_model = datamodel.get_table('spappresource') + specify_model = datamodel.get_table_strict('spappresource') # ID Field id = models.AutoField(primary_key=True, db_column='spappresourceid') @@ -5802,7 +5802,7 @@ class Meta: save = partialmethod(custom_save) class Spappresourcedata(models.Model): - specify_model = datamodel.get_table('spappresourcedata') + specify_model = datamodel.get_table_strict('spappresourcedata') # ID Field id = models.AutoField(primary_key=True, db_column='spappresourcedataid') @@ -5856,7 +5856,7 @@ def save_spappresourcedata(self, *args, **kwargs): save = partialmethod(save_spappresourcedata) class Spappresourcedir(models.Model): - specify_model = datamodel.get_table('spappresourcedir') + specify_model = datamodel.get_table_strict('spappresourcedir') # ID Field id = models.AutoField(primary_key=True, db_column='spappresourcedirid') @@ -5887,7 +5887,7 @@ class Meta: save = partialmethod(custom_save) class Spauditlog(models.Model): - specify_model = datamodel.get_table('spauditlog') + specify_model = datamodel.get_table_strict('spauditlog') # ID Field id = models.AutoField(primary_key=True, db_column='spauditlogid') @@ -5920,7 +5920,7 @@ def save_spauditlog(self, *args, **kwargs): save = partialmethod(save_spauditlog) class Spauditlogfield(models.Model): - specify_model = datamodel.get_table('spauditlogfield') + specify_model = datamodel.get_table_strict('spauditlogfield') # ID Field id = models.AutoField(primary_key=True, db_column='spauditlogfieldid') @@ -5946,7 +5946,7 @@ class Meta: save = partialmethod(custom_save) class Spexportschema(models.Model): - specify_model = datamodel.get_table('spexportschema') + specify_model = datamodel.get_table_strict('spexportschema') # ID Field id = models.AutoField(primary_key=True, db_column='spexportschemaid') @@ -5972,7 +5972,7 @@ class Meta: save = partialmethod(custom_save) class Spexportschemaitem(models.Model): - specify_model = datamodel.get_table('spexportschemaitem') + specify_model = datamodel.get_table_strict('spexportschemaitem') # ID Field id = models.AutoField(primary_key=True, db_column='spexportschemaitemid') @@ -6000,7 +6000,7 @@ class Meta: save = partialmethod(custom_save) class Spexportschemaitemmapping(models.Model): - specify_model = datamodel.get_table('spexportschemaitemmapping') + specify_model = datamodel.get_table_strict('spexportschemaitemmapping') # ID Field id = models.AutoField(primary_key=True, db_column='spexportschemaitemmappingid') @@ -6029,7 +6029,7 @@ class Meta: save = partialmethod(custom_save) class Spexportschemamapping(models.Model): - specify_model = datamodel.get_table('spexportschemamapping') + specify_model = datamodel.get_table_strict('spexportschemamapping') # ID Field id = models.AutoField(primary_key=True, db_column='spexportschemamappingid') @@ -6058,7 +6058,7 @@ class Meta: save = partialmethod(custom_save) class Spfieldvaluedefault(models.Model): - specify_model = datamodel.get_table('spfieldvaluedefault') + specify_model = datamodel.get_table_strict('spfieldvaluedefault') # ID Field id = models.AutoField(primary_key=True, db_column='spfieldvaluedefaultid') @@ -6088,7 +6088,7 @@ class Meta: save = partialmethod(custom_save) class Splocalecontainer(models.Model): - specify_model = datamodel.get_table('splocalecontainer') + specify_model = datamodel.get_table_strict('splocalecontainer') # ID Field id = models.AutoField(primary_key=True, db_column='splocalecontainerid') @@ -6124,7 +6124,7 @@ class Meta: save = partialmethod(custom_save) class Splocalecontaineritem(models.Model): - specify_model = datamodel.get_table('splocalecontaineritem') + specify_model = datamodel.get_table_strict('splocalecontaineritem') # ID Field id = models.AutoField(primary_key=True, db_column='splocalecontaineritemid') @@ -6159,7 +6159,7 @@ class Meta: save = partialmethod(custom_save) class Splocaleitemstr(models.Model): - specify_model = datamodel.get_table('splocaleitemstr') + specify_model = datamodel.get_table_strict('splocaleitemstr') # ID Field id = models.AutoField(primary_key=True, db_column='splocaleitemstrid') @@ -6193,7 +6193,7 @@ class Meta: save = partialmethod(custom_save) class Sppermission(models.Model): - specify_model = datamodel.get_table('sppermission') + specify_model = datamodel.get_table_strict('sppermission') # ID Field id = models.AutoField(primary_key=True, db_column='sppermissionid') @@ -6211,7 +6211,7 @@ class Meta: save = partialmethod(custom_save) class Spprincipal(models.Model): - specify_model = datamodel.get_table('spprincipal') + specify_model = datamodel.get_table_strict('spprincipal') # ID Field id = models.AutoField(primary_key=True, db_column='spprincipalid') @@ -6239,7 +6239,7 @@ class Meta: save = partialmethod(custom_save) class Spquery(models.Model): - specify_model = datamodel.get_table('spquery') + specify_model = datamodel.get_table_strict('spquery') # ID Field id = models.AutoField(primary_key=True, db_column='spqueryid') @@ -6277,7 +6277,7 @@ class Meta: save = partialmethod(custom_save) class Spqueryfield(models.Model): - specify_model = datamodel.get_table('spqueryfield') + specify_model = datamodel.get_table_strict('spqueryfield') # ID Field id = models.AutoField(primary_key=True, db_column='spqueryfieldid') @@ -6319,7 +6319,7 @@ class Meta: save = partialmethod(custom_save) class Spreport(models.Model): - specify_model = datamodel.get_table('spreport') + specify_model = datamodel.get_table_strict('spreport') # ID Field id = models.AutoField(primary_key=True, db_column='spreportid') @@ -6354,7 +6354,7 @@ class Meta: save = partialmethod(custom_save) class Spsymbiotainstance(models.Model): - specify_model = datamodel.get_table('spsymbiotainstance') + specify_model = datamodel.get_table_strict('spsymbiotainstance') # ID Field id = models.AutoField(primary_key=True, db_column='spsymbiotainstanceid') @@ -6388,7 +6388,7 @@ class Meta: save = partialmethod(custom_save) class Sptasksemaphore(models.Model): - specify_model = datamodel.get_table('sptasksemaphore') + specify_model = datamodel.get_table_strict('sptasksemaphore') # ID Field id = models.AutoField(primary_key=True, db_column='tasksemaphoreid') @@ -6420,7 +6420,7 @@ class Meta: save = partialmethod(custom_save) class Spversion(models.Model): - specify_model = datamodel.get_table('spversion') + specify_model = datamodel.get_table_strict('spversion') # ID Field id = models.AutoField(primary_key=True, db_column='spversionid') @@ -6448,7 +6448,7 @@ class Meta: save = partialmethod(custom_save) class Spviewsetobj(models.Model): - specify_model = datamodel.get_table('spviewsetobj') + specify_model = datamodel.get_table_strict('spviewsetobj') # ID Field id = models.AutoField(primary_key=True, db_column='spviewsetobjid') @@ -6479,7 +6479,7 @@ class Meta: save = partialmethod(custom_save) class Spvisualquery(models.Model): - specify_model = datamodel.get_table('spvisualquery') + specify_model = datamodel.get_table_strict('spvisualquery') # ID Field id = models.AutoField(primary_key=True, db_column='spvisualqueryid') @@ -6507,7 +6507,7 @@ class Meta: save = partialmethod(custom_save) class Specifyuser(model_extras.Specifyuser): - specify_model = datamodel.get_table('specifyuser') + specify_model = datamodel.get_table_strict('specifyuser') # ID Field id = models.AutoField(primary_key=True, db_column='specifyuserid') @@ -6539,7 +6539,7 @@ class Meta: # save = partialmethod(custom_save) class Storage(model_extras.Storage): - specify_model = datamodel.get_table('storage') + specify_model = datamodel.get_table_strict('storage') # ID Field id = models.AutoField(primary_key=True, db_column='storageid') @@ -6582,7 +6582,7 @@ class Meta: save = partialmethod(custom_save) class Storageattachment(models.Model): - specify_model = datamodel.get_table('storageattachment') + specify_model = datamodel.get_table_strict('storageattachment') # ID Field id = models.AutoField(primary_key=True, db_column='storageattachmentid') @@ -6608,7 +6608,7 @@ class Meta: save = partialmethod(custom_save) class Storagetreedef(models.Model): - specify_model = datamodel.get_table('storagetreedef') + specify_model = datamodel.get_table_strict('storagetreedef') # ID Field id = models.AutoField(primary_key=True, db_column='storagetreedefid') @@ -6634,7 +6634,7 @@ class Meta: save = partialmethod(custom_save) class Storagetreedefitem(model_extras.Storagetreedefitem): - specify_model = datamodel.get_table('storagetreedefitem') + specify_model = datamodel.get_table_strict('storagetreedefitem') # ID Field id = models.AutoField(primary_key=True, db_column='storagetreedefitemid') @@ -6667,7 +6667,7 @@ class Meta: save = partialmethod(custom_save) class Taxon(model_extras.Taxon): - specify_model = datamodel.get_table('taxon') + specify_model = datamodel.get_table_strict('taxon') # ID Field id = models.AutoField(primary_key=True, db_column='taxonid') @@ -6787,7 +6787,7 @@ class Meta: save = partialmethod(custom_save) class Taxonattachment(models.Model): - specify_model = datamodel.get_table('taxonattachment') + specify_model = datamodel.get_table_strict('taxonattachment') # ID Field id = models.AutoField(primary_key=True, db_column='taxonattachmentid') @@ -6813,7 +6813,7 @@ class Meta: save = partialmethod(custom_save) class Taxonattribute(models.Model): - specify_model = datamodel.get_table('taxonattribute') + specify_model = datamodel.get_table_strict('taxonattribute') # ID Field id = models.AutoField(primary_key=True, db_column='taxonattributeid') @@ -6999,7 +6999,7 @@ class Meta: save = partialmethod(custom_save) class Taxoncitation(models.Model): - specify_model = datamodel.get_table('taxoncitation') + specify_model = datamodel.get_table_strict('taxoncitation') # ID Field id = models.AutoField(primary_key=True, db_column='taxoncitationid') @@ -7034,7 +7034,7 @@ class Meta: save = partialmethod(custom_save) class Taxontreedef(models.Model): - specify_model = datamodel.get_table('taxontreedef') + specify_model = datamodel.get_table_strict('taxontreedef') # ID Field id = models.AutoField(primary_key=True, db_column='taxontreedefid') @@ -7062,7 +7062,7 @@ class Meta: save = partialmethod(custom_save) class Taxontreedefitem(model_extras.Taxontreedefitem): - specify_model = datamodel.get_table('taxontreedefitem') + specify_model = datamodel.get_table_strict('taxontreedefitem') # ID Field id = models.AutoField(primary_key=True, db_column='taxontreedefitemid') @@ -7096,7 +7096,7 @@ class Meta: save = partialmethod(custom_save) class Treatmentevent(models.Model): - specify_model = datamodel.get_table('treatmentevent') + specify_model = datamodel.get_table_strict('treatmentevent') # ID Field id = models.AutoField(primary_key=True, db_column='treatmenteventid') @@ -7154,7 +7154,7 @@ class Meta: save = partialmethod(custom_save) class Treatmenteventattachment(models.Model): - specify_model = datamodel.get_table('treatmenteventattachment') + specify_model = datamodel.get_table_strict('treatmenteventattachment') # ID Field id = models.AutoField(primary_key=True, db_column='treatmenteventattachmentid') @@ -7180,7 +7180,7 @@ class Meta: save = partialmethod(custom_save) class Voucherrelationship(models.Model): - specify_model = datamodel.get_table('voucherrelationship') + specify_model = datamodel.get_table_strict('voucherrelationship') # ID Field id = models.AutoField(primary_key=True, db_column='voucherrelationshipid') @@ -7224,7 +7224,7 @@ class Meta: save = partialmethod(custom_save) class Workbench(models.Model): - specify_model = datamodel.get_table('workbench') + specify_model = datamodel.get_table_strict('workbench') # ID Field id = models.AutoField(primary_key=True, db_column='workbenchid') @@ -7263,7 +7263,7 @@ class Meta: save = partialmethod(custom_save) class Workbenchdataitem(models.Model): - specify_model = datamodel.get_table('workbenchdataitem') + specify_model = datamodel.get_table_strict('workbenchdataitem') # ID Field id = models.AutoField(primary_key=True, db_column='workbenchdataitemid') @@ -7287,7 +7287,7 @@ class Meta: save = partialmethod(custom_save) class Workbenchrow(models.Model): - specify_model = datamodel.get_table('workbenchrow') + specify_model = datamodel.get_table_strict('workbenchrow') # ID Field id = models.AutoField(primary_key=True, db_column='workbenchrowid') @@ -7349,7 +7349,7 @@ def save_workbenchrow(self, *args, **kwargs): save = partialmethod(save_workbenchrow) class Workbenchrowexportedrelationship(models.Model): - specify_model = datamodel.get_table('workbenchrowexportedrelationship') + specify_model = datamodel.get_table_strict('workbenchrowexportedrelationship') # ID Field id = models.AutoField(primary_key=True, db_column='workbenchrowexportedrelationshipid') @@ -7376,7 +7376,7 @@ class Meta: save = partialmethod(custom_save) class Workbenchrowimage(models.Model): - specify_model = datamodel.get_table('workbenchrowimage') + specify_model = datamodel.get_table_strict('workbenchrowimage') # ID Field id = models.AutoField(primary_key=True, db_column='workbenchrowimageid') @@ -7426,7 +7426,7 @@ def save_workbenchrowimage(self, *args, **kwargs): save = partialmethod(save_workbenchrowimage) class Workbenchtemplate(models.Model): - specify_model = datamodel.get_table('workbenchtemplate') + specify_model = datamodel.get_table_strict('workbenchtemplate') # ID Field id = models.AutoField(primary_key=True, db_column='workbenchtemplateid') @@ -7452,7 +7452,7 @@ class Meta: save = partialmethod(custom_save) class Workbenchtemplatemappingitem(models.Model): - specify_model = datamodel.get_table('workbenchtemplatemappingitem') + specify_model = datamodel.get_table_strict('workbenchtemplatemappingitem') # ID Field id = models.AutoField(primary_key=True, db_column='workbenchtemplatemappingitemid') @@ -7492,7 +7492,7 @@ class Meta: save = partialmethod(custom_save) class Collectionobjecttype(models.Model): - specify_model = datamodel.get_table('collectionobjecttype') + specify_model = datamodel.get_table_strict('collectionobjecttype') # ID Field id = models.AutoField(primary_key=True, db_column='CollectionObjectTypeID') @@ -7520,7 +7520,7 @@ class Meta: save = partialmethod(custom_save) class Collectionobjectgrouptype(models.Model): - specify_model = datamodel.get_table('collectionobjectgrouptype') + specify_model = datamodel.get_table_strict('collectionobjectgrouptype') # ID Field id = models.AutoField(primary_key=True, db_column='COGTypeID') @@ -7544,7 +7544,7 @@ class Meta: save = partialmethod(custom_save) class Collectionobjectgroup(models.Model): # aka. Cog - specify_model = datamodel.get_table('collectionobjectgroup') + specify_model = datamodel.get_table_strict('collectionobjectgroup') # ID Field id = models.AutoField(primary_key=True, db_column='collectionobjectgroupid') @@ -7583,7 +7583,7 @@ class Meta: save = partialmethod(custom_save) class Collectionobjectgroupjoin(models.Model): # aka. CoJo or CogJoin - specify_model = datamodel.get_table('collectionobjectgroupjoin') + specify_model = datamodel.get_table_strict('collectionobjectgroupjoin') # ID Field id = models.AutoField(primary_key=True, db_column='collectionobjectgroupjoinid') @@ -7619,7 +7619,7 @@ class Meta: save = partialmethod(custom_save) class Absoluteage(models.Model): - specify_model = datamodel.get_table('absoluteage') + specify_model = datamodel.get_table_strict('absoluteage') # ID Field id = models.AutoField(db_column='AbsoluteAgeID', primary_key=True) @@ -7659,7 +7659,7 @@ class Meta: save = partialmethod(custom_save) class Relativeage(models.Model): - specify_model = datamodel.get_table('relativeage') + specify_model = datamodel.get_table_strict('relativeage') # ID Field id = models.AutoField(primary_key=True, db_column='relativeageid') @@ -7704,7 +7704,7 @@ class Meta: save = partialmethod(custom_save) class Absoluteageattachment(models.Model): - specify_model = datamodel.get_table('absoluteageattachment') + specify_model = datamodel.get_table_strict('absoluteageattachment') # ID Field id = models.AutoField(primary_key=True, db_column='absoluteageattachmentid') @@ -7730,7 +7730,7 @@ class Meta: save = partialmethod(custom_save) class Relativeageattachment(models.Model): - specify_model = datamodel.get_table('relativeageattachment') + specify_model = datamodel.get_table_strict('relativeageattachment') # ID Field id = models.AutoField(primary_key=True, db_column='relativeageattachmentid') @@ -7756,7 +7756,7 @@ class Meta: save = partialmethod(custom_save) class Absoluteagecitation(models.Model): - specify_model = datamodel.get_table('absoluteagecitation') + specify_model = datamodel.get_table_strict('absoluteagecitation') # ID Field id = models.AutoField(primary_key=True, db_column='absoluteagecitationid') @@ -7785,7 +7785,7 @@ class Meta: save = partialmethod(custom_save) class Relativeagecitation(models.Model): - specify_model = datamodel.get_table('relativeagecitation') + specify_model = datamodel.get_table_strict('relativeagecitation') # ID Field id = models.AutoField(primary_key=True, db_column='relativeagecitationid') @@ -7814,7 +7814,7 @@ class Meta: save = partialmethod(custom_save) class Tectonicunittreedef(models.Model): - specify_model = datamodel.get_table('tectonicunittreedef') + specify_model = datamodel.get_table_strict('tectonicunittreedef') # ID Field id = models.AutoField(primary_key=True, db_column='tectonicunittreedefid') @@ -7839,7 +7839,7 @@ class Meta: save = partialmethod(custom_save) class Tectonicunittreedefitem(model_extras.Tectonicunittreedefitem): - specify_model = datamodel.get_table('tectonicUnittreedefitem') + specify_model = datamodel.get_table_strict('tectonicUnittreedefitem') # ID Field id = models.AutoField(primary_key=True, db_column='tectonicunittreedefitemid') @@ -7871,7 +7871,7 @@ class Meta: save = partialmethod(custom_save) class Tectonicunit(model_extras.Tectonicunit): - specify_model = datamodel.get_table('tectonicunit') + specify_model = datamodel.get_table_strict('tectonicunit') # ID Field id = models.AutoField(primary_key=True, db_column='tectonicunitid') From 0f4de2babba85fcdcdc1eb16c4636f258a0a6c38 Mon Sep 17 00:00:00 2001 From: melton-jason Date: Mon, 6 Jan 2025 10:47:17 -0600 Subject: [PATCH 05/11] Reify types in datamodel --- specifyweb/specify/load_datamodel.py | 29 +++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/specifyweb/specify/load_datamodel.py b/specifyweb/specify/load_datamodel.py index 97b0ec86909..3c29bdb6d3d 100644 --- a/specifyweb/specify/load_datamodel.py +++ b/specifyweb/specify/load_datamodel.py @@ -201,7 +201,7 @@ def __init__(self, name: str = None, column: str = None, indexed: bool = None, length: int = None, is_relationship: bool = False): if not name: raise ValueError("name is required") - if not column and type == 'many-to-one': + if not column and not is_relationship: raise ValueError("column is required") self.is_relationship = is_relationship self.name = name or '' @@ -251,23 +251,26 @@ class Relationship(Field): type: str required: bool relatedModelName: str - column: str - otherSideName: str - - @property - def is_to_many(self) -> bool: - return 'to_many' in self.type + column: Optional[str] + otherSideName: Optional[str] def __init__(self, name: str = None, type: str = None, required: bool = None, - relatedModelName: str = None, column: str = None, - otherSideName: str = None, dependent: bool = False, is_relationship: bool = True, - is_to_many: bool = None): + relatedModelName: Optional[str] = None, column: Optional[str] = None, + otherSideName: Optional[str] = None, dependent: bool = False, is_relationship: bool = True): super().__init__(name, column, indexed=False, unique=False, required=required, type=type, length=0, is_relationship=is_relationship) + + if relatedModelName is None: + raise ValueError('relatedModelName is required for Relationship') + + if not column and type == 'many-to-one': + raise ValueError('column is required') + self.dependent = dependent if dependent is not None else False - self.relatedModelName = relatedModelName or '' - self.otherSideName = otherSideName or '' - # self.is_to_many = is_to_many if is_to_many is not None else 'to_many' in self.type + self.column = column + self.relatedModelName = relatedModelName + self.otherSideName = otherSideName + def make_table(tabledef: ElementTree.Element) -> Table: iddef = tabledef.find('id') From 07df6391e86d7ea0ed688193c3c7689529ef9eaa Mon Sep 17 00:00:00 2001 From: melton-jason Date: Mon, 6 Jan 2025 10:47:47 -0600 Subject: [PATCH 06/11] Remove redundant relationships from api requests --- specifyweb/specify/api.py | 63 +++++++++++++++++++++++++++------------ 1 file changed, 44 insertions(+), 19 deletions(-) diff --git a/specifyweb/specify/api.py b/specifyweb/specify/api.py index 854b8d258a3..450dfed45b7 100644 --- a/specifyweb/specify/api.py +++ b/specifyweb/specify/api.py @@ -8,6 +8,7 @@ import re from typing import Any, Dict, List, Optional, Tuple, Iterable, Union, \ Callable, TypedDict, cast + from urllib.parse import urlencode from typing_extensions import TypedDict @@ -18,7 +19,7 @@ from django import forms from django.db import transaction -from django.db.models import F +from django.db.models import F, Model from django.apps import apps from django.http import (HttpResponse, HttpResponseBadRequest, Http404, HttpResponseNotAllowed, QueryDict) @@ -33,7 +34,7 @@ from .uiformatters import AutonumberOverflowException from .filter_by_col import filter_by_collection from .auditlog import auditlog -from .datamodel import datamodel +from .datamodel import datamodel, Table, Relationship from .calculated_fields import calculate_extra_fields ReadPermChecker = Callable[[Any], None] @@ -429,19 +430,40 @@ def _maybe_delete(data: Dict[str, Any], to_delete: str): if to_delete in data: del data[to_delete] -def cleanData(model, data: Dict[str, Any], agent) -> Dict[str, Any]: - """Returns a copy of data with only fields that are part of model, removing - metadata fields and warning on unexpected extra fields.""" +def _is_circular_relationship(model, field_name: str, parent_relationship: Optional[Relationship] = None) -> bool: + table: Table = cast(Table, model.specify_model) + field = table.get_field(field_name) + + if field is None or parent_relationship is None: + return False + + if not field.is_relationship or parent_relationship.otherSideName is None or field.otherSideName is None: + return False + + return datamodel.reverse_relationship(cast(Relationship, field)) is parent_relationship + +def cleanData(model, data: Dict[str, Any], parent_relationship: Optional[Relationship] = None) -> Dict[str, Any]: + """Returns a copy of data with redundant resources removed and only + fields that are part of model, removing metadata fields and warning on + unexpected extra fields""" cleaned = {} for field_name in list(data.keys()): if field_name in ('resource_uri', 'recordset_info', '_tableName'): # These fields are meta data, not part of the resource. continue + try: db_field_name = correct_field_name(model, field_name) except FieldDoesNotExist: logger.warn('field "%s" does not exist in %s', field_name, model) else: + if _is_circular_relationship(model, db_field_name, parent_relationship): + """If this would add a redundant resource - e.g., + Accession -> collectionObjects -> accession - then omit the + final resource from the cleaned data + """ + logger.warn(f"circular/redundant relationship {parent_relationship.name} -> {db_field_name} found in data. Skipping update/create of {db_field_name}") + continue cleaned[db_field_name] = data[field_name] # Unset date precision if date is not set, but precision is @@ -476,12 +498,13 @@ def cleanData(model, data: Dict[str, Any], agent) -> Dict[str, Any]: return cleaned -def create_obj(collection, agent, model, data: Dict[str, Any], parent_obj=None): +def create_obj(collection, agent, model, data: Dict[str, Any], parent_obj=None, parent_relationship=None): """Create a new instance of 'model' and populate it with 'data'.""" logger.debug("creating %s with data: %s", model, data) if isinstance(model, str): model = get_model_or_404(model) - data = cleanData(model, data, agent) + + data = cleanData(model, data, parent_relationship) obj = model() handle_fk_fields(collection, agent, obj, data) set_fields_from_data(obj, data) @@ -512,7 +535,7 @@ def fld_change_info(obj, field, val) -> Optional[FieldChangeInfo]: return {'field_name': field.name, 'old_value': old_value, 'new_value': value} return None -def set_fields_from_data(obj, data: Dict[str, Any]) -> List[FieldChangeInfo]: +def set_fields_from_data(obj: Model, data: Dict[str, Any]) -> List[FieldChangeInfo]: """Where 'obj' is a Django model instance and 'data' is a dict, set all fields provided by data that are not related object fields. """ @@ -614,8 +637,8 @@ def handle_fk_fields(collection, agent, obj, data: Dict[str, Any]) -> Tuple[List elif hasattr(val, 'items'): # i.e. it's a dict of some sort # The related object is represented by a nested dict of data. rel_model = field.related_model - - rel_obj = update_or_create_resource(collection, agent, rel_model, val, obj if dependent else None) + datamodel_field = obj.specify_model.get_relationship(field_name) + rel_obj = update_or_create_resource(collection, agent, rel_model, val, obj if dependent else None, datamodel_field) setattr(obj, field_name, rel_obj) if dependent and old_related and old_related.id != rel_obj.id: @@ -624,6 +647,7 @@ def handle_fk_fields(collection, agent, obj, data: Dict[str, Any]) -> Tuple[List data[field_name] = _obj_to_data(rel_obj, read_checker) else: raise Exception(f'bad foreign key field in data: {field_name}') + if str(old_related_id) != str(new_related_id): dirty.append({'field_name': field_name, 'old_value': old_related_id, 'new_value': new_related_id}) @@ -665,12 +689,12 @@ def _handle_dependent_to_many(collection, agent, obj, field, value): assert isinstance(value, list), "didn't get inline data for dependent field %s in %s: %r" % (field.name, obj, value) rel_model = field.related_model - ids = [] # Ids not in this list will be deleted (if dependent) or removed from obj (if independent) at the end. + ids = [] # Ids not in this list will be deleted at the end. for rel_data in value: rel_data[field.field.name] = obj - - rel_obj = update_or_create_resource(collection, agent, rel_model, rel_data, parent_obj=obj) + datamodel_field = obj.specify_model.get_relationship(field.field.name) + rel_obj = update_or_create_resource(collection, agent, rel_model, rel_data, parent_obj=obj, parent_relationship=datamodel_field) ids.append(rel_obj.id) # Record the id as one to keep. @@ -721,7 +745,8 @@ def _handle_independent_to_many(collection, agent, obj, field, value: Independen rel_data = raw_rel_data rel_data[field.field.name] = obj - update_or_create_resource(collection, agent, rel_model, rel_data, None) + datamodel_field = obj.specify_model.get_relationship(field.field.name) + update_or_create_resource(collection, agent, rel_model, rel_data, parent_obj=None, parent_relationship=datamodel_field) if len(to_remove) > 0: assert obj.pk is not None, f"Unable to remove {obj.__class__.__name__}.{field.field.name} resources from new {obj.__class__.__name__}" @@ -736,14 +761,14 @@ def _handle_independent_to_many(collection, agent, obj, field, value: Independen rel_data[field.field.name] = None update_obj(collection, agent, rel_model, rel_data["id"], rel_data["version"], rel_data) -def update_or_create_resource(collection, agent, model, data, parent_obj): +def update_or_create_resource(collection, agent, model, data, parent_obj, parent_relationship=None): if 'id' in data: return update_obj(collection, agent, model, data['id'], data['version'], data, - parent_obj=parent_obj) + parent_obj=parent_obj, parent_relationship=parent_relationship) else: - return create_obj(collection, agent, model, data, parent_obj=parent_obj) + return create_obj(collection, agent, model, data, parent_obj=parent_obj, parent_relationship=parent_relationship) @transaction.atomic def delete_resource(collection, agent, name, id, version) -> None: @@ -782,14 +807,14 @@ def delete_obj(obj, version=None, parent_obj=None, collection=None, agent=None, def put_resource(collection, agent, name: str, id, version, data: Dict[str, Any]): return update_obj(collection, agent, name, id, version, data) -def update_obj(collection, agent, name: str, id, version, data: Dict[str, Any], parent_obj=None): +def update_obj(collection, agent, name: str, id, version, data: Dict[str, Any], parent_obj=None, parent_relationship=None): """Update the resource with 'id' in model named 'name' with given 'data'. """ obj = get_object_or_404(name, id=int(id)) check_table_permissions(collection, agent, obj, "update") - data = cleanData(obj.__class__, data, agent) + data = cleanData(obj.__class__, data, parent_relationship) dependents_to_delete, fk_dirty = handle_fk_fields(collection, agent, obj, data) dirty = fk_dirty + set_fields_from_data(obj, data) From 1c93cf5e09bdafb37f9bb06982b17d1a5f963046 Mon Sep 17 00:00:00 2001 From: melton-jason Date: Mon, 6 Jan 2025 10:48:15 -0600 Subject: [PATCH 07/11] Write automated tests for removing redundant relationships --- specifyweb/specify/tests/test_api.py | 32 ++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/specifyweb/specify/tests/test_api.py b/specifyweb/specify/tests/test_api.py index 23ac6c17255..c83c68095ba 100644 --- a/specifyweb/specify/tests/test_api.py +++ b/specifyweb/specify/tests/test_api.py @@ -658,6 +658,38 @@ def test_reassigning_independent_to_many(self): self.collectionobjects[1].refresh_from_db() self.assertEqual(self.collectionobjects[0].accession, acc2) self.assertEqual(self.collectionobjects[1].accession, acc2) + + def test_skipping_redundant_resources(self): + catalog_number = f'num-{len(self.collectionobjects)}' + redundant_catalog_number = f'num-{len(self.collectionobjects) + 1}' + redundant_accession_number = 'c' + accession_data = { + 'accessionnumber': "b", + 'division': api.uri_for_model('division', self.division.id), + 'collectionobjects': { + "update": [ + { + "catalogNumber": catalog_number, + 'accession': { + "accessionNumber": redundant_accession_number, + }, + "determinations": [ + { + "collectionObject": { + "catalogNumber": redundant_catalog_number + }, + } + ] + } + ] + } + } + + accession = api.create_obj(self.collection, self.agent, 'Accession', accession_data) + self.assertFalse(models.Accession.objects.filter(accessionnumber=redundant_accession_number).exists()) + self.assertFalse(models.Collectionobject.objects.filter(catalognumber=redundant_catalog_number).exists()) + co = models.Collectionobject.objects.get(catalognumber=catalog_number) + self.assertEqual(len(co.determinations.all()), 0) # version control on inlined resources should be tested From c7224963f9b3dd8dbaf4d1e61e55e2940baa258f Mon Sep 17 00:00:00 2001 From: melton-jason Date: Mon, 6 Jan 2025 16:52:59 +0000 Subject: [PATCH 08/11] Lint code with ESLint and Prettier Triggered by f357cd3f57985510f2a14ecceaad7edef6075476 on branch refs/heads/issue-5508 --- .../js_src/lib/components/DataModel/resourceApi.ts | 2 +- .../FormSliders/RecordSelectorFromCollection.tsx | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/specifyweb/frontend/js_src/lib/components/DataModel/resourceApi.ts b/specifyweb/frontend/js_src/lib/components/DataModel/resourceApi.ts index 85bb7830dee..71b3affc2c6 100644 --- a/specifyweb/frontend/js_src/lib/components/DataModel/resourceApi.ts +++ b/specifyweb/frontend/js_src/lib/components/DataModel/resourceApi.ts @@ -896,7 +896,7 @@ export const ResourceBase = Backbone.Model.extend({ }); // Check added to avoid infinite loop in following forEach for collectionRelationship see https://github.com/specify/specify7/issues/6025 - if (self.specifyTable.name === 'CollectionRelationship') return json + if (self.specifyTable.name === 'CollectionRelationship') return json; Object.entries(self.independentResources).forEach( ([fieldName, related]) => { diff --git a/specifyweb/frontend/js_src/lib/components/FormSliders/RecordSelectorFromCollection.tsx b/specifyweb/frontend/js_src/lib/components/FormSliders/RecordSelectorFromCollection.tsx index f00c8ab3ac6..aa2c572b570 100644 --- a/specifyweb/frontend/js_src/lib/components/FormSliders/RecordSelectorFromCollection.tsx +++ b/specifyweb/frontend/js_src/lib/components/FormSliders/RecordSelectorFromCollection.tsx @@ -86,11 +86,11 @@ export function RecordSelectorFromCollection({ isLazy && collection.related?.isNew() !== true && collection.models[index] === undefined - ){ - if (typeof handleFetch === 'function' ) { - handleFetch?.() + ) { + if (typeof handleFetch === 'function') { + handleFetch?.(); } else { - void collection.fetch() + void collection.fetch(); } } }, [collection, isLazy, index, records.length, isToOne, handleFetch]); From 6b9ca585c76e436f5a25bb268606f982059f5e21 Mon Sep 17 00:00:00 2001 From: melton-jason Date: Mon, 6 Jan 2025 11:00:07 -0600 Subject: [PATCH 09/11] Fix mypy errors --- specifyweb/specify/api.py | 5 +++-- specifyweb/specify/load_datamodel.py | 18 ++++++++++-------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/specifyweb/specify/api.py b/specifyweb/specify/api.py index 450dfed45b7..c284c3b0777 100644 --- a/specifyweb/specify/api.py +++ b/specifyweb/specify/api.py @@ -437,7 +437,7 @@ def _is_circular_relationship(model, field_name: str, parent_relationship: Optio if field is None or parent_relationship is None: return False - if not field.is_relationship or parent_relationship.otherSideName is None or field.otherSideName is None: + if not field.is_relationship or parent_relationship.otherSideName is None or cast(Relationship, field).otherSideName is None: return False return datamodel.reverse_relationship(cast(Relationship, field)) is parent_relationship @@ -458,11 +458,12 @@ def cleanData(model, data: Dict[str, Any], parent_relationship: Optional[Relatio logger.warn('field "%s" does not exist in %s', field_name, model) else: if _is_circular_relationship(model, db_field_name, parent_relationship): + parent_name: str = getattr(parent_relationship, 'name', '') """If this would add a redundant resource - e.g., Accession -> collectionObjects -> accession - then omit the final resource from the cleaned data """ - logger.warn(f"circular/redundant relationship {parent_relationship.name} -> {db_field_name} found in data. Skipping update/create of {db_field_name}") + logger.warn(f"circular/redundant relationship {parent_name} -> {db_field_name} found in data. Skipping update/create of {db_field_name}") continue cleaned[db_field_name] = data[field_name] diff --git a/specifyweb/specify/load_datamodel.py b/specifyweb/specify/load_datamodel.py index 3c29bdb6d3d..421aea8dbec 100644 --- a/specifyweb/specify/load_datamodel.py +++ b/specifyweb/specify/load_datamodel.py @@ -1,4 +1,4 @@ -from typing import List, Dict, Union, Optional, Iterable, TypeVar, Callable +from typing import List, Dict, Union, Optional, Iterable, TypeVar, Callable, cast from xml.etree import ElementTree import os import warnings @@ -57,7 +57,7 @@ def get_table_by_id_strict(self, table_id: int, strict: bool=False) -> 'Table': def reverse_relationship(self, relationship: 'Relationship') -> Optional['Relationship']: if hasattr(relationship, 'otherSideName'): - return self.get_table_strict(relationship.relatedModelName).get_relationship(relationship.otherSideName) + return self.get_table_strict(relationship.relatedModelName).get_relationship(cast(str, relationship.otherSideName)) else: return None @@ -189,27 +189,29 @@ def __repr__(self) -> str: class Field(object): is_relationship: bool = False name: str - column: str + column: Optional[str] indexed: bool unique: bool required: bool = False - type: str + type: Optional[str] length: Optional[int] - def __init__(self, name: str = None, column: str = None, indexed: bool = None, + def __init__(self, name: str = None, column: Optional[str] = None, indexed: bool = None, unique: bool = None, required: bool = None, type: str = None, length: int = None, is_relationship: bool = False): if not name: raise ValueError("name is required") + if not type: + raise ValueError('type is required') if not column and not is_relationship: raise ValueError("column is required") self.is_relationship = is_relationship - self.name = name or '' - self.column = column or '' + self.name = name + self.column = column self.indexed = indexed if indexed is not None else False self.unique = unique if unique is not None else False self.required = required if required is not None else False - self.type = type if type is not None else '' + self.type = type self.length = length if length is not None else None def __repr__(self) -> str: From 1f5e69a9bc2281c0f59750c53b3adcb2be53b125 Mon Sep 17 00:00:00 2001 From: melton-jason Date: Mon, 6 Jan 2025 11:44:44 -0600 Subject: [PATCH 10/11] Use correct field name in toMany relationships --- specifyweb/specify/api.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/specifyweb/specify/api.py b/specifyweb/specify/api.py index c284c3b0777..b1d0929d721 100644 --- a/specifyweb/specify/api.py +++ b/specifyweb/specify/api.py @@ -694,7 +694,8 @@ def _handle_dependent_to_many(collection, agent, obj, field, value): for rel_data in value: rel_data[field.field.name] = obj - datamodel_field = obj.specify_model.get_relationship(field.field.name) + datamodel_field = obj.specify_model.get_relationship(field.name) + rel_obj = update_or_create_resource(collection, agent, rel_model, rel_data, parent_obj=obj, parent_relationship=datamodel_field) ids.append(rel_obj.id) # Record the id as one to keep. @@ -746,7 +747,7 @@ def _handle_independent_to_many(collection, agent, obj, field, value: Independen rel_data = raw_rel_data rel_data[field.field.name] = obj - datamodel_field = obj.specify_model.get_relationship(field.field.name) + datamodel_field = obj.specify_model.get_relationship(field.name) update_or_create_resource(collection, agent, rel_model, rel_data, parent_obj=None, parent_relationship=datamodel_field) if len(to_remove) > 0: From 1af8b2456cfc90a12d76fc95958b21dc36782a0b Mon Sep 17 00:00:00 2001 From: melton-jason Date: Mon, 6 Jan 2025 13:19:35 -0600 Subject: [PATCH 11/11] Don't check redundant relationships for toMany --- specifyweb/specify/api.py | 11 ++++------- specifyweb/specify/tests/test_api.py | 6 +++--- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/specifyweb/specify/api.py b/specifyweb/specify/api.py index b1d0929d721..c382b75096a 100644 --- a/specifyweb/specify/api.py +++ b/specifyweb/specify/api.py @@ -455,7 +455,7 @@ def cleanData(model, data: Dict[str, Any], parent_relationship: Optional[Relatio try: db_field_name = correct_field_name(model, field_name) except FieldDoesNotExist: - logger.warn('field "%s" does not exist in %s', field_name, model) + logger.warning('field "%s" does not exist in %s', field_name, model) else: if _is_circular_relationship(model, db_field_name, parent_relationship): parent_name: str = getattr(parent_relationship, 'name', '') @@ -463,7 +463,7 @@ def cleanData(model, data: Dict[str, Any], parent_relationship: Optional[Relatio Accession -> collectionObjects -> accession - then omit the final resource from the cleaned data """ - logger.warn(f"circular/redundant relationship {parent_name} -> {db_field_name} found in data. Skipping update/create of {db_field_name}") + logger.warning(f"circular/redundant relationship {parent_name} -> {db_field_name} found in data. Skipping update/create of {db_field_name}") continue cleaned[db_field_name] = data[field_name] @@ -694,9 +694,7 @@ def _handle_dependent_to_many(collection, agent, obj, field, value): for rel_data in value: rel_data[field.field.name] = obj - datamodel_field = obj.specify_model.get_relationship(field.name) - - rel_obj = update_or_create_resource(collection, agent, rel_model, rel_data, parent_obj=obj, parent_relationship=datamodel_field) + rel_obj = update_or_create_resource(collection, agent, rel_model, rel_data, parent_obj=obj) ids.append(rel_obj.id) # Record the id as one to keep. @@ -747,8 +745,7 @@ def _handle_independent_to_many(collection, agent, obj, field, value: Independen rel_data = raw_rel_data rel_data[field.field.name] = obj - datamodel_field = obj.specify_model.get_relationship(field.name) - update_or_create_resource(collection, agent, rel_model, rel_data, parent_obj=None, parent_relationship=datamodel_field) + update_or_create_resource(collection, agent, rel_model, rel_data, parent_obj=None) if len(to_remove) > 0: assert obj.pk is not None, f"Unable to remove {obj.__class__.__name__}.{field.field.name} resources from new {obj.__class__.__name__}" diff --git a/specifyweb/specify/tests/test_api.py b/specifyweb/specify/tests/test_api.py index c83c68095ba..0c0aa22a489 100644 --- a/specifyweb/specify/tests/test_api.py +++ b/specifyweb/specify/tests/test_api.py @@ -675,11 +675,13 @@ def test_skipping_redundant_resources(self): }, "determinations": [ { + "text1": "test determination", "collectionObject": { "catalogNumber": redundant_catalog_number }, } - ] + ], + 'collection': api.uri_for_model('Collection', self.collection.id), } ] } @@ -688,8 +690,6 @@ def test_skipping_redundant_resources(self): accession = api.create_obj(self.collection, self.agent, 'Accession', accession_data) self.assertFalse(models.Accession.objects.filter(accessionnumber=redundant_accession_number).exists()) self.assertFalse(models.Collectionobject.objects.filter(catalognumber=redundant_catalog_number).exists()) - co = models.Collectionobject.objects.get(catalognumber=catalog_number) - self.assertEqual(len(co.determinations.all()), 0) # version control on inlined resources should be tested