Skip to content

Commit

Permalink
Merge branch 'v7.9-dev' into issue-3890
Browse files Browse the repository at this point in the history
  • Loading branch information
melton-jason committed Sep 7, 2023
2 parents c35ed41 + bbd6df3 commit 6c161db
Show file tree
Hide file tree
Showing 59 changed files with 1,751 additions and 1,197 deletions.
2 changes: 2 additions & 0 deletions specifyweb/businessrules/attachment_rules.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import re

from django.db.models import Max

from .orm_signal_handler import orm_signal_handler
from specifyweb.specify.scoping import Scoping
from specifyweb.specify import models
Expand Down
1 change: 0 additions & 1 deletion specifyweb/businessrules/collector_rules.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from .exceptions import BusinessRuleException
from django.db.models import Max
from .orm_signal_handler import orm_signal_handler
from specifyweb.specify.models import Collector
Expand Down
1 change: 0 additions & 1 deletion specifyweb/businessrules/determiner_rules.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from .exceptions import BusinessRuleException
from django.db.models import Max
from .orm_signal_handler import orm_signal_handler
from specifyweb.specify import models
Expand Down
12 changes: 12 additions & 0 deletions specifyweb/businessrules/extractor_rules.py
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
12 changes: 12 additions & 0 deletions specifyweb/businessrules/fieldnotebook_rules.py
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
1 change: 1 addition & 0 deletions specifyweb/businessrules/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@
accessionagent_rules,
fundingagent_rules,
determiner_rules,
extractor_rules,
)
12 changes: 12 additions & 0 deletions specifyweb/businessrules/pcrperson_rules.py
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
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);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,8 @@ describe('getUniqueFields', () => {
]));
test('AccessionAgent', () =>
expect(getUniqueFields(schema.models.AccessionAgent)).toEqual([
'role',
'agent',
'timestampCreated',
'version',
'timestampModified',
Expand Down

0 comments on commit 6c161db

Please sign in to comment.