Skip to content

Commit 88ac7a1

Browse files
authored
Merge pull request #33 from buildingSMART/fix/IVS-442_ParseInfo_Fails_With_Unique_Constraint_Error
IVS-442 - Parse info fails with unique constraint error
2 parents 4d889c6 + 6c1f3a0 commit 88ac7a1

File tree

3 files changed

+65
-2
lines changed

3 files changed

+65
-2
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Generated by Django 5.1.6 on 2025-03-13 18:44
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('ifc_validation_models', '0009_company_email_address_pattern'),
10+
]
11+
12+
operations = [
13+
migrations.RemoveConstraint(
14+
model_name='authoringtool',
15+
name='unique_name_version',
16+
),
17+
migrations.AddConstraint(
18+
model_name='authoringtool',
19+
constraint=models.UniqueConstraint(fields=('name', 'version', 'company_id'), name='unique_name_version_company_id'),
20+
),
21+
migrations.AddConstraint(
22+
model_name='authoringtool',
23+
constraint=models.UniqueConstraint(condition=models.Q(('company_id__isnull', True)), fields=('name', 'version'), name='unique_name_version_company_id_null'),
24+
),
25+
]

models.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import threading
33

44
from django.db import models
5+
from django.db.models import Q
56
from django.conf import settings
67
from django.core.exceptions import ImproperlyConfigured
78
from django.utils import timezone
@@ -319,7 +320,17 @@ class Meta:
319320
verbose_name_plural = "Authoring Tools"
320321

321322
constraints = [
322-
models.UniqueConstraint(fields=['name', 'version'], name='unique_name_version')
323+
# Postgres supports NULLS DISTINCT, but not all DB's do (Sqlite does not!) - hence workaround using two constraints
324+
# models.UniqueConstraint(fields=['name', 'version', 'company_id'], name='unique_name_version_company', nulls_distinct=False)
325+
models.UniqueConstraint(
326+
name='unique_name_version_company_id',
327+
fields=['name', 'version', 'company_id']
328+
),
329+
models.UniqueConstraint(
330+
name='unique_name_version_company_id_null',
331+
fields=['name', 'version'],
332+
condition=Q(company_id__isnull=True)
333+
)
323334
]
324335

325336
def __str__(self):

tests.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,22 @@ def test_find_tool_by_full_name_should_succeed3(self):
224224
self.assertEqual(found_tool.name, tool1.name)
225225
self.assertIsNone(found_tool.company)
226226

227+
def test_find_tool_by_full_name_should_succeed4(self):
228+
229+
# arrange
230+
ValidationModelsTestCase.set_user_context()
231+
tool1 = AuthoringTool.objects.create(name='MyFabTool', version='1.0')
232+
233+
# act
234+
name_to_find = 'MyFabTool - 1.0'
235+
found_tool = AuthoringTool.find_by_full_name(name_to_find)
236+
237+
# assert
238+
self.assertIsNotNone(found_tool)
239+
self.assertIsInstance(found_tool, AuthoringTool)
240+
self.assertEqual(found_tool.name, tool1.name)
241+
self.assertIsNone(found_tool.company)
242+
227243
def test_find_tool_by_full_name_should_return_none(self):
228244

229245
# arrange
@@ -248,6 +264,18 @@ def test_add_tool_twice_should_fail(self):
248264
with self.assertRaises(IntegrityError):
249265
AuthoringTool.objects.create(name='Test Application', version='0.11') # should fail
250266

267+
def test_add_tool_with_company_twice_should_fail(self):
268+
269+
# arrange
270+
ValidationModelsTestCase.set_user_context()
271+
272+
# act/assert
273+
company, _ = Company.objects.get_or_create(name='Acme Inc.')
274+
AuthoringTool.objects.create(name='Test Application', version='0.10', company=company) # should succeed
275+
AuthoringTool.objects.create(name='Test Application', version='0.11', company=company) # should succeed
276+
with self.assertRaises(IntegrityError):
277+
AuthoringTool.objects.create(name='Test Application', version='0.11', company=company) # should fail
278+
251279
def test_model_can_navigate_back_to_request(self):
252280

253281
# arrange
@@ -305,4 +333,3 @@ def test_task_can_navigate_back_to_model(self):
305333
# assert
306334
self.assertIsNotNone(retrieved_task)
307335
self.assertEqual(model.id, model_id)
308-

0 commit comments

Comments
 (0)