-
Notifications
You must be signed in to change notification settings - Fork 38
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'v7.9-dev' into issue-3890
- Loading branch information
Showing
59 changed files
with
1,751 additions
and
1,197 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
from django.db.models import Max | ||
from .orm_signal_handler import orm_signal_handler | ||
from specifyweb.specify.models import Extractor | ||
|
||
@orm_signal_handler('pre_save', 'Extractor') | ||
def collector_pre_save(extractor): | ||
if extractor.id is None: | ||
if extractor.ordernumber is None: | ||
# this should be atomic, but whatever | ||
others = Extractor.objects.filter(dnasequence=extractor.dnasequence) | ||
top = others.aggregate(Max('ordernumber'))['ordernumber__max'] | ||
extractor.ordernumber = 0 if top is None else top + 1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
from django.db.models import Max | ||
from .orm_signal_handler import orm_signal_handler | ||
from specifyweb.specify.models import Fieldnotebookpageset | ||
|
||
@orm_signal_handler('pre_save', 'Fieldnotebookpageset') | ||
def collector_pre_save(pageset): | ||
if pageset.id is None: | ||
if pageset.ordernumber is None: | ||
# this should be atomic, but whatever | ||
others = Fieldnotebookpageset.objects.filter(fieldnotebook=pageset.fieldnotebook) | ||
top = others.aggregate(Max('ordernumber'))['ordernumber__max'] | ||
pageset.ordernumber = 0 if top is None else top + 1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,4 +20,5 @@ | |
accessionagent_rules, | ||
fundingagent_rules, | ||
determiner_rules, | ||
extractor_rules, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
from django.db.models import Max | ||
from .orm_signal_handler import orm_signal_handler | ||
from specifyweb.specify.models import Pcrperson | ||
|
||
@orm_signal_handler('pre_save', 'Pcrperson') | ||
def collector_pre_save(pcr_person): | ||
if pcr_person.id is None: | ||
if pcr_person.ordernumber is None: | ||
# this should be atomic, but whatever | ||
others = Pcrperson.objects.filter(dnasequence=pcr_person.dnasequence) | ||
top = others.aggregate(Max('ordernumber'))['ordernumber__max'] | ||
pcr_person.ordernumber = 0 if top is None else top + 1 |
290 changes: 126 additions & 164 deletions
290
specifyweb/frontend/js_src/lib/components/DataModel/__tests__/businessRules.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,194 +1,156 @@ | ||
import { overrideAjax } from '../../../tests/ajax'; | ||
import { mockTime, requireContext } from '../../../tests/helpers'; | ||
import { overwriteReadOnly } from '../../../utils/types'; | ||
import { businessRuleDefs } from '../businessRuleDefs'; | ||
import { SerializedModel } from '../helperTypes'; | ||
import { getResourceApiUrl } from '../resource'; | ||
import { schema } from '../schema'; | ||
import { Determination } from '../types'; | ||
|
||
mockTime(); | ||
requireContext(); | ||
|
||
test('uniqueness rules assigned correctly', async () => { | ||
const accessionAgentUniquenessRules = { | ||
role: [ | ||
describe('uniqueness rules assigned correctly', () => { | ||
test('otherField uniqueness rule assigned', async () => { | ||
expect(businessRuleDefs.AccessionAgent?.uniqueIn).toMatchInlineSnapshot(` | ||
{ | ||
field: 'accession', | ||
otherFields: ['agent'], | ||
}, | ||
{ | ||
field: 'repositoryagreement', | ||
otherFields: ['agent'], | ||
}, | ||
], | ||
agent: [ | ||
"agent": [ | ||
{ | ||
"field": "accession", | ||
"otherFields": [ | ||
"role", | ||
], | ||
}, | ||
{ | ||
"field": "repositoryagreement", | ||
"otherFields": [ | ||
"role", | ||
], | ||
}, | ||
], | ||
"role": [ | ||
{ | ||
"field": "accession", | ||
"otherFields": [ | ||
"agent", | ||
], | ||
}, | ||
{ | ||
"field": "repositoryagreement", | ||
"otherFields": [ | ||
"agent", | ||
], | ||
}, | ||
], | ||
} | ||
`); | ||
}); | ||
|
||
test('Standard rules assigned correctly', async () => { | ||
expect(businessRuleDefs.CollectionObject?.uniqueIn).toMatchInlineSnapshot(` | ||
{ | ||
field: 'accession', | ||
otherFields: ['role'], | ||
}, | ||
"catalogNumber": [ | ||
"collection", | ||
], | ||
"guid": [ | ||
"institution", | ||
], | ||
"uniqueIdentifier": [ | ||
undefined, | ||
], | ||
} | ||
`); | ||
}); | ||
|
||
test('JSON nulls are converted to undefined', async () => { | ||
expect(businessRuleDefs.Permit?.uniqueIn).toMatchInlineSnapshot(` | ||
{ | ||
field: 'repositoryagreement', | ||
otherFields: ['role'], | ||
}, | ||
], | ||
}; | ||
expect(businessRuleDefs.AccessionAgent?.uniqueIn).toBe( | ||
accessionAgentUniquenessRules | ||
); | ||
"permitNumber": [ | ||
undefined, | ||
], | ||
} | ||
`); | ||
}); | ||
}); | ||
|
||
const determinationId = 321; | ||
const determinationUrl = getResourceApiUrl('Determination', determinationId); | ||
const determinationResponse: Partial<SerializedModel<Determination>> = { | ||
id: determinationId, | ||
resource_uri: determinationUrl, | ||
}; | ||
|
||
const collectionObjectId = 220; | ||
const collectionObjectUrl = getResourceApiUrl( | ||
'CollectionObject', | ||
collectionObjectId | ||
); | ||
const collectionObjectResponse = { | ||
id: collectionObjectId, | ||
resource_uri: collectionObjectUrl, | ||
catalognumber: '000022002', | ||
collection: getResourceApiUrl('Collection', 4), | ||
determinations: determinationUrl, | ||
}; | ||
|
||
overrideAjax(collectionObjectUrl, collectionObjectResponse); | ||
overrideAjax(determinationUrl, determinationResponse); | ||
|
||
describe('business rules', () => { | ||
test('collectionObject customInit', async () => { | ||
const resource = new schema.models.CollectionObject.Resource({ | ||
id: collectionObjectId, | ||
describe('Borrow Material business rules', () => { | ||
const borrowMaterialId = 1; | ||
const borrowMaterialUrl = getResourceApiUrl( | ||
'BorrowMaterial', | ||
borrowMaterialId | ||
); | ||
|
||
const getBaseBorrowMaterial = () => | ||
new schema.models.BorrowMaterial.Resource({ | ||
id: borrowMaterialId, | ||
resource_uri: borrowMaterialUrl, | ||
quantity: 20, | ||
quantityreturned: 13, | ||
quantityresolved: 15, | ||
}); | ||
await resource.fetch(); | ||
expect(resource.get('collectingEvent')).toBeDefined(); | ||
resource.save(); | ||
|
||
test('fieldCheck quantityReturned', async () => { | ||
const borrowMaterial = getBaseBorrowMaterial(); | ||
|
||
borrowMaterial.set('quantityReturned', 30); | ||
expect(borrowMaterial.get('quantityReturned')).toBe(15); | ||
}); | ||
|
||
describe('determination business rules', () => { | ||
test('determination customInit', async () => { | ||
const determination = new schema.models.Determination.Resource({ | ||
id: determinationId, | ||
}); | ||
await determination.fetch(); | ||
expect(determination.get('isCurrent')).toBe(true); | ||
}); | ||
test('only one determination isCurrent', async () => { | ||
const determination = new schema.models.Determination.Resource({ | ||
id: determinationId, | ||
}); | ||
const resource = new schema.models.CollectionObject.Resource({ | ||
id: collectionObjectId, | ||
}); | ||
await resource.rgetCollection('determinations').then((collection) => { | ||
collection.add(new schema.models.Determination.Resource()); | ||
}); | ||
expect(determination.get('isCurrent')).toBe(false); | ||
}); | ||
test('determination taxon field check', async () => { | ||
const determination = new schema.models.Determination.Resource({ | ||
id: determinationId, | ||
}); | ||
const taxonId = 19345; | ||
const taxonUrl = getResourceApiUrl('Taxon', taxonId); | ||
const taxonResponse = { | ||
resource_uri: getResourceApiUrl('Taxon', taxonUrl), | ||
id: taxonId, | ||
name: 'melas', | ||
fullName: 'Ameiurus melas', | ||
}; | ||
overrideAjax(taxonUrl, taxonResponse); | ||
determination.set( | ||
'taxon', | ||
new schema.models.Taxon.Resource({ | ||
id: taxonId, | ||
}) | ||
); | ||
expect(determination.get('preferredTaxon')).toBe(taxonUrl); | ||
}); | ||
test('fieldCheck quantityResolved', async () => { | ||
const borrowMaterial = getBaseBorrowMaterial(); | ||
|
||
borrowMaterial.set('quantityResolved', 30); | ||
expect(borrowMaterial.get('quantityResolved')).toBe(20); | ||
|
||
borrowMaterial.set('quantityResolved', 5); | ||
expect(borrowMaterial.get('quantityResolved')).toBe(13); | ||
}); | ||
}); | ||
|
||
test('dnaSequence genesequence fieldCheck', async () => { | ||
const dnaSequence = new schema.models.DNASequence.Resource({ | ||
id: 1, | ||
describe('Collection Object business rules', () => { | ||
const collectionObjectlId = 2; | ||
const collectionObjectUrl = getResourceApiUrl( | ||
'CollectionObject', | ||
collectionObjectlId | ||
); | ||
|
||
const getBaseCollectionObject = () => | ||
new schema.models.CollectionObject.Resource({ | ||
id: collectionObjectlId, | ||
resource_uri: collectionObjectUrl, | ||
}); | ||
dnaSequence.set('geneSequence', 'cat123gaaz'); | ||
|
||
expect(dnaSequence.get('totalResidues')).toBe(10); | ||
expect(dnaSequence.get('compA')).toBe(3); | ||
expect(dnaSequence.get('ambiguousResidues')).toBe(4); | ||
const orginalEmbeddedCollectingEvent = schema.embeddedCollectingEvent; | ||
|
||
beforeAll(() => { | ||
overwriteReadOnly(schema, 'embeddedCollectingEvent', true); | ||
}); | ||
}); | ||
|
||
describe('uniquenessRules', () => { | ||
const permitOneId = 1; | ||
const permitOneUrl = '/api/specify/permit/1/'; | ||
const permitOneResponse = { | ||
id: permitOneId, | ||
resource_uri: permitOneUrl, | ||
permitNumber: '20', | ||
}; | ||
overrideAjax(permitOneUrl, permitOneResponse); | ||
|
||
const permitTwoId = 2; | ||
const permitTwoUrl = getResourceApiUrl('Permit', permitTwoId); | ||
const permitTwoResponse = { | ||
id: permitTwoId, | ||
resource_uri: permitTwoUrl, | ||
permitNumber: '20', | ||
}; | ||
overrideAjax(permitTwoUrl, permitTwoResponse); | ||
|
||
overrideAjax(getResourceApiUrl('CollectionObject', 221), { | ||
id: 221, | ||
resource_uri: getResourceApiUrl('CollectionObject', 221), | ||
catalogNumber: '000022002', | ||
afterAll(() => { | ||
overwriteReadOnly( | ||
schema, | ||
'embeddedCollectingEvent', | ||
orginalEmbeddedCollectingEvent | ||
); | ||
}); | ||
|
||
test('global uniquenessRule', async () => { | ||
const testPermit = new schema.models.Permit.Resource({ | ||
id: permitOneId, | ||
permitNumber: '20', | ||
}); | ||
await testPermit.save(); | ||
test('CollectionObject customInit', async () => { | ||
const collectionObject = getBaseCollectionObject(); | ||
|
||
const duplicatePermit = new schema.models.Permit.Resource({ | ||
id: permitTwoId, | ||
permitNumber: '20', | ||
}); | ||
expect( | ||
duplicatePermit | ||
.fetch() | ||
.then((permit) => | ||
permit.businessRuleManager?.checkField('permitNumber') | ||
) | ||
).resolves.toBe({ | ||
key: 'br-uniqueness-permitnumber', | ||
valid: false, | ||
reason: 'Value must be unique to Database', | ||
}); | ||
expect(collectionObject.get('collectingEvent')).toBeDefined(); | ||
}); | ||
}); | ||
|
||
test('scoped uniqueness rule', async () => { | ||
const resource = new schema.models.CollectionObject.Resource({ | ||
id: 221, | ||
catalogNumber: '000022002', | ||
}); | ||
expect( | ||
resource | ||
.fetch() | ||
.then((collectionObject) => | ||
collectionObject.businessRuleManager?.checkField('catalogNumber') | ||
) | ||
).resolves.toBe({ | ||
key: 'br-uniqueness-catalognumber', | ||
valid: false, | ||
reason: 'Value must be unique to Collection', | ||
describe('DNASequence business rules', () => { | ||
test('fieldCheck geneSequence', async () => { | ||
const dNASequence = new schema.models.DNASequence.Resource({ | ||
id: 1, | ||
}); | ||
dNASequence.set('geneSequence', 'aaa ttttt gg c zzzz'); | ||
|
||
await dNASequence.businessRuleManager?.checkField('geneSequence'); | ||
|
||
expect(dNASequence.get('compA')).toBe(3); | ||
expect(dNASequence.get('compT')).toBe(5); | ||
expect(dNASequence.get('compG')).toBe(2); | ||
expect(dNASequence.get('compC')).toBe(1); | ||
expect(dNASequence.get('ambiguousResidues')).toBe(4); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.