Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow configuring uniqueness rules #4215

Merged
merged 171 commits into from
Jan 16, 2024
Merged
Show file tree
Hide file tree
Changes from 142 commits
Commits
Show all changes
171 commits
Select commit Hold shift + click to select a range
acbc7a3
Add initial models
melton-jason Oct 5, 2023
dd21b11
Add Django migrations
melton-jason Oct 5, 2023
3c10fe4
Add uniqueness rule endpoints
melton-jason Oct 5, 2023
b982a75
Fetch and cache uniqueness rules
melton-jason Oct 5, 2023
389d4bc
Fetch uniqueness rules on initial context
melton-jason Oct 5, 2023
4a8adaa
Remove old uniqueness rule definitions
melton-jason Oct 5, 2023
d73e82b
Refactor frontend business rule fetching
melton-jason Oct 5, 2023
9161843
Add frontend UI for configuring uniqueness
melton-jason Oct 5, 2023
a16af8e
Add localization strings
melton-jason Oct 5, 2023
e07e2af
Merge branch 'v7.9-dev' into uniqueness-rules
melton-jason Oct 5, 2023
01c60ff
Merge branch 'production' into uniqueness-rules
melton-jason Oct 5, 2023
65a013b
Merge branch 'production' into uniqueness-rules
melton-jason Oct 5, 2023
1fd83b7
Remove old uniqueness rule tests
melton-jason Oct 5, 2023
604b661
Add helper funciton to get cached uniqueness rules
melton-jason Oct 5, 2023
3ab7b16
Change getUniqueFields function to reflect changes
melton-jason Oct 5, 2023
83ed3fb
Add static uniquenessRule source for tests
melton-jason Oct 5, 2023
e75efde
use getField over getRelationship when getting scope field
melton-jason Oct 5, 2023
ca8631c
Remove unused imports
melton-jason Oct 5, 2023
975bf79
Add option for loose duplicate search dependant on required fields
melton-jason Oct 5, 2023
af50993
Lint code with ESLint and Prettier
melton-jason Oct 5, 2023
bee7d59
Revert "Lint code with ESLint and Prettier"
melton-jason Oct 5, 2023
e11baea
Fetch scope object before setting it to uniqueness rule
melton-jason Oct 5, 2023
6039b89
Restrict scope to relationships
melton-jason Oct 5, 2023
ec509af
Update README.md
grantfitzsimmons Oct 6, 2023
8974970
Revert "Merge branch 'production' of https://github.com/specify/speci…
grantfitzsimmons Oct 6, 2023
9c40a82
Update CITATION.cff
grantfitzsimmons Oct 8, 2023
56f1ec8
Changed Update RSS Feed from Cloud to RSS. Added RSS Icon to Specify'…
carlosmbe Oct 6, 2023
383897f
Lint code with ESLint and Prettier
carlosmbe Oct 6, 2023
c7fa2fe
Remove cloud icon as it is no longer needed
grantfitzsimmons Oct 7, 2023
c8e92b1
Sync localization strings with Weblate
hgdly Oct 9, 2023
8eb49df
Make docker build for arm64 in GitHub Actions workflow (#4068)
acwhite211 Oct 10, 2023
e7293cc
Reformat UniquenessRule to use a table format
melton-jason Oct 17, 2023
f38872b
Resolve ESLint errors/cleanup
melton-jason Oct 17, 2023
86394ee
Cleanup endpoint code
melton-jason Oct 17, 2023
ffd4e2d
Merge remote-tracking branch 'origin/production' into uniqueness-rules
melton-jason Oct 17, 2023
5a8cfd4
[django-migration] Only create rules for containers with 0 schema type
melton-jason Oct 18, 2023
1286f0a
Refactor business rules
melton-jason Oct 23, 2023
743f752
Merge remote-tracking branch 'origin/production' into uniqueness-rules
melton-jason Oct 23, 2023
6aa76b5
[unique-rules] make the table design more intuitive
melton-jason Nov 2, 2023
ff1638f
[unique-rules] Add handleRemoved callback
melton-jason Nov 2, 2023
0ed6e53
Resolve typescript errors
melton-jason Nov 3, 2023
e6c6f72
Use recource_uri for unique field identifier over index
melton-jason Nov 3, 2023
b654224
Assign a frontend unique identifier for each uniqueness rule
melton-jason Nov 8, 2023
f08aef9
Fix typescript errors
melton-jason Nov 8, 2023
f02022f
Define explicit Database scope and misc. bug fixes
melton-jason Nov 13, 2023
c7f78e1
Allow exporting found duplicate values to csv
melton-jason Nov 15, 2023
f3778c6
Add better backend support for unique rules
melton-jason Nov 16, 2023
107c01d
Add -to-one relatiobships to UniqueFields picklist
melton-jason Nov 16, 2023
cd0fc87
Remove uneeded post_save signal
melton-jason Nov 22, 2023
32b1f0c
Initialize uniqueness rules post-migrate
melton-jason Nov 22, 2023
aa516d2
Migrate default uniqueness rules to flat file
melton-jason Nov 22, 2023
104734f
Add openapi schema to uniqueness rule endpoint
melton-jason Nov 22, 2023
d9ff5e1
Rename isdatabaseconstraint to isDatabaseConstraint
melton-jason Nov 22, 2023
cfadf10
Handle uniqueness rule being in different scope from instance
melton-jason Nov 24, 2023
04fce55
Remove redundant scope overrides in scoping.py
melton-jason Nov 24, 2023
3247ed1
Return Model instances over IDs when fetching scopes
melton-jason Nov 24, 2023
4cc50ab
Add backend scoping tests
melton-jason Nov 24, 2023
e3100b7
Wait until each changed promsie is resolved
melton-jason Nov 24, 2023
c8fcdec
Cleanup frontend uniqueness rule code
melton-jason Nov 24, 2023
079ebd6
Migrate scope to many-to-many join table
melton-jason Nov 29, 2023
c626490
Always use static databaseScope on frontend over null
melton-jason Nov 29, 2023
21cdf69
Misc. bug fixes
melton-jason Nov 29, 2023
527683c
Refactor uniqueness rule validation endpoint
melton-jason Dec 1, 2023
b73ec19
Add openapi schema for rule validation endpoint
melton-jason Dec 1, 2023
f54ce53
Use field name over resource_uri for identifier
melton-jason Dec 4, 2023
f92ef66
Use array for scope rather than splocalecontaineritem or null
melton-jason Dec 5, 2023
2a0e9d2
[bus-rules] Refactor checkUnique to accept an array for scope
melton-jason Dec 6, 2023
dd514ef
Update static uniqueness rule test files
melton-jason Dec 6, 2023
e71a004
Remove strict search from validation endpoint
melton-jason Dec 7, 2023
bcf9dad
Memoize uniquefield and scope picklist items
melton-jason Dec 8, 2023
16383c6
Further optimize field/relationships being passed to UniquenessRuleRow
melton-jason Dec 8, 2023
eb70879
Remove uniqueIdRef and move validation results onto rule
melton-jason Dec 8, 2023
e4b07f9
Properly Update expanded rules when rule is removed
melton-jason Dec 9, 2023
81b8c16
Minor simplifications to react states
realVinayak Dec 9, 2023
1f17d02
Revert to using effect for save blocker
realVinayak Dec 9, 2023
3a772f8
Use POST if tableRules contains a new rule
melton-jason Dec 11, 2023
c87494a
Prevent duplicate fields in a uniqueness rule
melton-jason Dec 12, 2023
0fa7f78
Don't cache uniqueness rules
melton-jason Dec 12, 2023
6629bd0
Simplify useTableUniquenessRules
melton-jason Dec 14, 2023
9452a88
[frontend-tests] Reformat static uniqueness file
melton-jason Dec 14, 2023
d4a3889
Ensure tables is imported before rules are fetched
melton-jason Dec 18, 2023
a891898
Resolve misc. PR comments
melton-jason Dec 18, 2023
d6d65ff
Add permissions for modifying uniqueness rules
melton-jason Dec 18, 2023
684249f
Optimize permission checks on backend
melton-jason Dec 18, 2023
7055085
Delete removed uniqueness rules
melton-jason Dec 18, 2023
874b72b
Further cleanup businessRules.ts
melton-jason Dec 19, 2023
48bed4b
Simply uniqueIn function
melton-jason Dec 19, 2023
7e76f07
Centralize field information and fix bugs
melton-jason Dec 20, 2023
a2894f2
Disconnect previous signal handlers
melton-jason Dec 20, 2023
29bc2c4
handle case when matchable is empty dictionary
melton-jason Dec 20, 2023
43cd376
[backend] improve signal handling
melton-jason Dec 20, 2023
69c576d
Be consistent using string for models over model instances
melton-jason Dec 20, 2023
a563b94
Simplify type and handle case when `model` is None
melton-jason Dec 20, 2023
892859c
Initialize uniqueness rules when app is loaded
melton-jason Dec 21, 2023
a373e96
Manually resolve cases from xml-editor -> production
melton-jason Dec 21, 2023
cafdcb2
Merge remote-tracking branch 'origin/production' into uniqueness-rule…
melton-jason Dec 21, 2023
30dc7f1
Fix import
melton-jason Dec 21, 2023
fc162d2
Resolve typescript errors
melton-jason Dec 22, 2023
3aea634
Ensure the uniqueness_rule dispatch uids are unique
melton-jason Dec 22, 2023
413b1a2
Only initialize Uniqueness Rules for tests and runserver
melton-jason Dec 22, 2023
fb7546c
Handle finding rules to remove on the backend
melton-jason Dec 22, 2023
71a9878
Remove unnecessary import
melton-jason Dec 22, 2023
078e734
Begin resolving backend tests for uniqueness rules
melton-jason Dec 26, 2023
86101be
Misc. bug fixes and optimizations
melton-jason Dec 27, 2023
86f8272
Remove old uniqueness_rules.json file
melton-jason Dec 27, 2023
4180359
Strongly reference uniqueness signal handlers
melton-jason Dec 27, 2023
50bef16
Improve error formatting when DEBUG mode is disabled
melton-jason Dec 27, 2023
328be3d
Handle case when every rule is deleted for a table
melton-jason Dec 27, 2023
754f969
[jest] Reformat uniqueness rule file for tests
melton-jason Dec 28, 2023
6cfa421
Make layout more intuitive
melton-jason Dec 28, 2023
9feac2a
Further improve UX
melton-jason Dec 28, 2023
e9d1152
Use string field names over splocalecontaineritems
melton-jason Dec 30, 2023
6e19d72
Fix typescript errors
melton-jason Dec 30, 2023
235b871
Merge branch 'production' into uniqueness-rules
melton-jason Dec 30, 2023
d4927d3
Remove unused localization strings
melton-jason Dec 30, 2023
11a1db1
Don't create splocalecontainers for backend tests
melton-jason Dec 30, 2023
e86ce2b
Ensure datamodel is always loaded when fetching rules
melton-jason Jan 2, 2024
6da5291
Disable save button when no changes have been made
melton-jason Jan 2, 2024
bedf0c0
Fix Duplicate Exports
melton-jason Jan 2, 2024
8145e85
Remove close button in ModifyUniquenessRule when duplicates present
melton-jason Jan 2, 2024
5722169
Make "Invalid unique reason" subheader
melton-jason Jan 2, 2024
0d37056
Add dimensionsKey to remember dialog size
melton-jason Jan 2, 2024
0080ab9
Improve UniquenessRuleScope Picker
melton-jason Jan 2, 2024
7b3a153
Replace pencil with warning icon when rule has duplicates
melton-jason Jan 2, 2024
55cbb9c
Use one signal for checking uniqueness
melton-jason Jan 3, 2024
881ef5a
Only run check_unique when uniqueness migrations have been run
melton-jason Jan 3, 2024
cbed777
Resolve failing backend tests
melton-jason Jan 4, 2024
a4d49a1
Remove 'examples' from validation endpoint open schema
melton-jason Jan 4, 2024
2e2ec7d
Merge branch 'production' into uniqueness-rules
melton-jason Jan 4, 2024
d705bf3
Remove un-needed dialogClassNames import
melton-jason Jan 4, 2024
5d880de
Merge branch 'uniqueness-rules' of https://github.com/specify/specify…
melton-jason Jan 4, 2024
eedcdc8
Memoize defaultScope
melton-jason Jan 5, 2024
64b4191
Simplify allowing advance scopes on backend
melton-jason Jan 5, 2024
e0f0b8c
Better support preparations on the backend
melton-jason Jan 5, 2024
b5c6615
Properly remove uniqueness rules
melton-jason Jan 5, 2024
6acf0c6
Write unit tests for uniqueness rules
melton-jason Jan 5, 2024
43aee5b
Resolve simple code comments
melton-jason Jan 5, 2024
b8b63d4
Use Form in uniqueness rules
melton-jason Jan 5, 2024
66ad376
Integrate validation with Uniqueness Rules
melton-jason Jan 5, 2024
f6d02f5
Merge branch 'production' into uniqueness-rules
melton-jason Jan 5, 2024
f710ae7
Rename label prop on uniqueness rule components
melton-jason Jan 5, 2024
81aefa3
Merge branch 'uniqueness-rules' of https://github.com/specify/specify…
melton-jason Jan 5, 2024
8bd846f
Better support constructing filters for backend
melton-jason Jan 7, 2024
6df0559
Fix type in collection test
melton-jason Jan 7, 2024
d7af32e
Split UniquenessRules.tsx
melton-jason Jan 7, 2024
9f2e56f
Resolve UI/UX comments
melton-jason Jan 8, 2024
29d995f
Give uniquenessrules its own route
melton-jason Jan 9, 2024
0f76c7b
Ensure changesMade is updated when cached rules change
melton-jason Jan 9, 2024
8b472b2
Merge branch 'production' into uniqueness-rules
melton-jason Jan 9, 2024
4fc1df9
Define a contsant for Backbone's field separator
melton-jason Jan 10, 2024
212b83b
Remove the need for getValueFromPath
melton-jason Jan 10, 2024
231288c
Lock needed tables from rules when field is auto-incremented
melton-jason Jan 10, 2024
868f7ec
Remove uniqueness rule permissions
melton-jason Jan 10, 2024
40db442
Resolve failing tests
melton-jason Jan 10, 2024
73bd4da
Fix uniqueness rule test
melton-jason Jan 10, 2024
9fb00fb
Merge branch 'production' into uniqueness-rules
melton-jason Jan 10, 2024
0dc2876
Reorganize businessrules directory
melton-jason Jan 11, 2024
dbfde4b
Make the uniqueness dialog modal and integrate unload protect
melton-jason Jan 11, 2024
4aabd81
Use mapping path when updating line data over rule scope
melton-jason Jan 11, 2024
1e5d966
Handle case when setting scope as subset of mapping path
melton-jason Jan 11, 2024
37e1104
Make rules explicitly scoped higher than division global
melton-jason Jan 12, 2024
55eab87
Set initial value of stored uniqueness rules to empty array
melton-jason Jan 12, 2024
16fab98
Make the add uniqueness rule button in line with dialog buttons
melton-jason Jan 12, 2024
c4b84fd
Use JSON.stringigy to parse field values
melton-jason Jan 12, 2024
a6ab931
Merge branch 'production' into uniqueness-rules
melton-jason Jan 12, 2024
28f4546
Left-justify Add Uniqueness Rule button and remove border
melton-jason Jan 12, 2024
3e8d239
Remove zero-to-one relationships from field/scope
melton-jason Jan 14, 2024
bf5fffe
Filter out NULL scopes for validation
melton-jason Jan 14, 2024
ad247e9
Make column headers in duplicates csv clearer
melton-jason Jan 14, 2024
0e6d751
Merge branch 'production' into uniqueness-rules
melton-jason Jan 15, 2024
a02cff0
Move AddUniquenessField button inline with the last Uniqueness Field
melton-jason Jan 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
18 changes: 13 additions & 5 deletions specifyweb/businessrules/attachment_rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,36 +16,44 @@
tables_with_attachments = {getattr(models, model.__name__.replace('attachment', ''))
for model in attachment_tables}


@orm_signal_handler('pre_save')
def attachment_jointable_save(sender, obj):
if sender not in attachment_tables: return
if sender not in attachment_tables:
return

if obj.attachment_id is None: raise AbortSave()
if obj.attachment_id is None:
raise AbortSave()

attachee = get_attachee(obj)
obj.attachment.tableid = attachee.specify_model.tableId
obj.attachment.scopetype, obj.attachment.scopeid = Scoping(attachee)()
scopetype, scope = Scoping(attachee)()
obj.attachment.scopetype, obj.attachment.scopeid = scopetype, scope.id
obj.attachment.save()


@orm_signal_handler('post_delete')
def attachment_jointable_deletion(sender, obj):
if sender in attachment_tables:
obj.attachment.delete()


@orm_signal_handler('pre_save', 'Attachment')
def attachment_save(attachment):
if attachment.id is None and attachment.tableid is None:
# since tableid cannot be null, use Attachment table id as placeholder.
# the actual table id will be set when the join row is saved. (see above)
attachment.tableid = models.Attachment.specify_model.tableId


@orm_signal_handler('post_delete', 'Attachment')
def attachment_deletion(attachment):
from specifyweb.attachment_gw.views import delete_attachment_file
if attachment.attachmentlocation is not None:
delete_attachment_file(attachment.attachmentlocation)


def get_attachee(jointable_inst):
main_table_name = JOINTABLE_NAME_RE.match(jointable_inst.__class__.__name__).group(1)
main_table_name = JOINTABLE_NAME_RE.match(
jointable_inst.__class__.__name__).group(1)
return getattr(jointable_inst, main_table_name.lower())

40 changes: 40 additions & 0 deletions specifyweb/businessrules/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Generated by Django 3.2.15 on 2023-12-28 22:48

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

initial = True

dependencies = [
('specify', '__first__'),
]

operations = [
migrations.CreateModel(
name='UniquenessRule',
fields=[
('id', models.AutoField(db_column='uniquenessruleid', primary_key=True, serialize=False, verbose_name='uniquenessruleid')),
('isDatabaseConstraint', models.BooleanField(db_column='isDatabaseConstraint', default=False)),
('modelName', models.CharField(max_length=256)),
('discipline', models.ForeignKey(db_column='DisciplineID', on_delete=django.db.models.deletion.PROTECT, to='specify.discipline')),
],
options={
'db_table': 'uniquenessrule',
},
),
migrations.CreateModel(
name='UniquenessRule_Field',
fields=[
('uniquenessrule_fieldid', models.AutoField(primary_key=True, serialize=False, verbose_name='uniquenessrule_fieldsid')),
('fieldPath', models.TextField(blank=True, null=True)),
('isScope', models.BooleanField(default=False)),
('uniquenessrule', models.ForeignKey(db_column='uniquenessruleid', on_delete=django.db.models.deletion.CASCADE, to='businessrules.uniquenessrule')),
],
options={
'db_table': 'uniquenessrule_fields',
},
),
]
22 changes: 22 additions & 0 deletions specifyweb/businessrules/migrations/0002_default_unique_rules.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from django.db import migrations

from specifyweb.specify import models as spmodels
from specifyweb.businessrules.uniqueness_rules import apply_default_uniqueness_rules


def apply_rules_to_discipline(apps, schema_editor):
for disp in spmodels.Discipline.objects.all():
apply_default_uniqueness_rules(disp)


class Migration(migrations.Migration):
initial = True

dependencies = [
('specify', '__first__'),
('businessrules', '0001_initial'),
]

operations = [
migrations.RunPython(apply_rules_to_discipline),
]
Empty file.
68 changes: 67 additions & 1 deletion specifyweb/businessrules/models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from django.db import models

from specifyweb.specify import models as spmodels

from . import (
uniqueness_rules,
recordset_rules,
collector_rules,
author_rules,
Expand All @@ -21,4 +24,67 @@
fundingagent_rules,
determiner_rules,
extractor_rules,
preparation_rules
)


class PsuedoManyToManyManager(models.Manager):
def __init__(self, base_instance, through_model, through_field) -> None:
self.base_instance = base_instance
self.through_model = through_model
self.through_field = through_field.field.name

self._related_field_name = [
field.name for field in through_model._meta.fields if field.related_model is not None][0]

def all(self) -> models.QuerySet:
return self.through_model.objects.filter(**{self._related_field_name: self.base_instance})

def filter(self, *args, **kwargs) -> models.QuerySet:
return self.all().filter(*args, **kwargs)

def clear(self):
return self.all().delete()

def add(self, *args, through_defaults={}):
for item in args:
to_create = {
self._related_field_name: self.base_instance,
self.through_field: item}

for field, value in through_defaults.items():
to_create[field] = value
self.through_model.objects.create(**to_create)

def set(self, iterable, through_defaults={}):
self.clear()
self.add(*iterable, through_defaults=through_defaults)


class UniquenessRule(models.Model):
id = models.AutoField('uniquenessruleid',
primary_key=True, db_column='uniquenessruleid')
isDatabaseConstraint = models.BooleanField(
default=False, db_column='isDatabaseConstraint')
modelName = models.CharField(max_length=256)
discipline = models.ForeignKey(
spmodels.Discipline, on_delete=models.PROTECT, db_column="DisciplineID")

@property
def fields(self):
return PsuedoManyToManyManager(self, UniquenessRule_Field, UniquenessRule_Field.fieldPath)

class Meta:
db_table = 'uniquenessrule'


class UniquenessRule_Field(models.Model):
uniquenessrule_fieldid = models.AutoField(
'uniquenessrule_fieldsid', primary_key=True)
uniquenessrule = models.ForeignKey(
UniquenessRule, on_delete=models.CASCADE, db_column='uniquenessruleid')
fieldPath = models.TextField(null=True, blank=True)
isScope = models.BooleanField(default=False)

class Meta:
db_table = "uniquenessrule_fields"
30 changes: 26 additions & 4 deletions specifyweb/businessrules/orm_signal_handler.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,44 @@
from typing import Callable, Literal, Optional, Hashable

from django.db.models import signals
from django.dispatch import receiver

from specifyweb.specify import models

def orm_signal_handler(signal, model=None):
# See https://docs.djangoproject.com/en/3.2/ref/signals/#module-django.db.models.signals
MODEL_SIGNAL = Literal["pre_init", "post_init", "pre_save",
"post_save", "pre_delete", "post_delete", "m2m_changed"]


def orm_signal_handler(signal: MODEL_SIGNAL, model: Optional[str] = None, **kwargs):
def _dec(rule):
receiver_kwargs = {}
receiver_kwargs = kwargs
if model is not None:
receiver_kwargs['sender'] = getattr(models, model)

def handler(sender, **kwargs):
if kwargs.get('raw', False): return
if kwargs.get('raw', False):
return
# since the rule knows what model the signal comes from
# the sender value is redundant.
rule(kwargs['instance'])
else:
def handler(sender, **kwargs):
if kwargs.get('raw', False): return
if kwargs.get('raw', False):
return
rule(sender, kwargs['instance'])

return receiver(getattr(signals, signal), **receiver_kwargs)(handler)
return _dec


def disconnect_signal(signal: MODEL_SIGNAL, model_name: Optional[str] = None, dispatch_uid: Optional[Hashable] = None) -> bool:
fetched_signal = getattr(signals, signal)
django_model = None if model_name is None else getattr(models, model_name)
return fetched_signal.disconnect(
sender=django_model, dispatch_uid=dispatch_uid)

def connect_signal(signal: MODEL_SIGNAL, callback: Callable, model_name: Optional[str] = None, dispatch_uid: Optional[Hashable] = None):
fetched_signal = getattr(signals, signal)
django_model = None if model_name is None else getattr(models, model_name)
return fetched_signal.connect(callback, sender=django_model, dispatch_uid=dispatch_uid)
7 changes: 7 additions & 0 deletions specifyweb/businessrules/preparation_rules.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from .orm_signal_handler import orm_signal_handler


@orm_signal_handler('pre_save', 'Preparation')
def preparation_pre_save(preparation):
if preparation.collectionmemberid is None:
preparation.collectionmemberid = preparation.collectionobject.collectionmemberid
3 changes: 2 additions & 1 deletion specifyweb/businessrules/tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,5 @@
from .storagetreedefitem import *
from .taxon import *
from .taxontreedefitem import *

from .preparation import *
from .uniqueness_rules import *
1 change: 1 addition & 0 deletions specifyweb/businessrules/tests/accessionagent.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from specifyweb.specify.api_tests import ApiTests
from ..exceptions import BusinessRuleException


class AccessionAgentTests(ApiTests):
@skip("rule was removed in 17e82c6157")
def test_no_duped_agents_in_accession(self):
Expand Down
2 changes: 2 additions & 0 deletions specifyweb/businessrules/tests/permit.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from specifyweb.specify.api_tests import ApiTests
from ..exceptions import BusinessRuleException


class PermitTests(ApiTests):
def test_number_is_unique(self):
models.Permit.objects.create(
Expand All @@ -11,6 +12,7 @@ def test_number_is_unique(self):

with self.assertRaises(BusinessRuleException):
models.Permit.objects.create(
institution=self.institution,
permitnumber='1')

models.Permit.objects.create(
Expand Down
57 changes: 57 additions & 0 deletions specifyweb/businessrules/tests/preparation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from specifyweb.specify import models
from specifyweb.specify.api_tests import ApiTests
from ..exceptions import BusinessRuleException


class PreparationTests(ApiTests):
def test_barcode_unique_to_collection(self):
prep_type = models.Preptype.objects.create(
name='testPrepType',
isloanable=False,
collection=self.collection,
)

models.Preparation.objects.create(
collectionobject=self.collectionobjects[0],
barcode='1',
preptype=prep_type
)
with self.assertRaises(BusinessRuleException):
models.Preparation.objects.create(
collectionobject=self.collectionobjects[0],
barcode='1',
preptype=prep_type
)
with self.assertRaises(BusinessRuleException):
models.Preparation.objects.create(
collectionobject=self.collectionobjects[1],
barcode='1',
preptype=prep_type
)
models.Preparation.objects.create(
collectionobject=self.collectionobjects[0],
barcode='2',
preptype=prep_type
)

other_collection = models.Collection.objects.create(
catalognumformatname='test',
collectionname='OtherCollection',
isembeddedcollectingevent=False,
discipline=self.discipline)

other_co = models.Collectionobject.objects.create(
catalognumber='num-1',
collection=other_collection,
)
other_preptype = models.Preptype.objects.create(
name='otherPrepType',
isloanable=False,
collection=other_collection,
)

models.Preparation.objects.create(
collectionobject=other_co,
barcode='1',
preptype=other_preptype
)