Skip to content

Commit 8a189b9

Browse files
committed
feat(model): res position unique index;
- Create a unique index for active resource positions.
1 parent 2c85ee9 commit 8a189b9

File tree

4 files changed

+27
-2
lines changed

4 files changed

+27
-2
lines changed

ckan/lib/dictization/model_save.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ def package_resource_list_save(
138138
# Mark any left-over resources as deleted
139139
for resource in set(old_list) - set(obj_list):
140140
resource.state = 'deleted'
141+
resource.position = None
141142
resource_list.append(resource)
142143

143144
return True

ckan/migration/versions/109_e12e991f586b_create_res_position_constraint.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
"""Create con_package_resource_unique_position constraint
22
to make resource positions unique per package.
33
4+
Sets deleted resource positions to null.
5+
46
Revision ID: e12e991f586b
57
Revises: f7b64c701a10
68
Create Date: 2026-02-10 18:26:31.122945
@@ -18,6 +20,11 @@
1820

1921

2022
def upgrade():
23+
op.execute("""
24+
UPDATE resource SET position=null WHERE state='deleted';
25+
""")
26+
print('Set position=null for deleted resources.')
27+
2128
op.create_unique_constraint(
2229
constraint_name='con_package_resource_unique_position',
2330
table_name='resource', columns=['package_id', 'position'],

ckan/model/resource.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from __future__ import annotations
33

44
import datetime
5-
from typing import Any, Callable, ClassVar, Optional, cast
5+
from typing import Any, Callable, ClassVar, Optional, List, cast
66

77

88
from collections import OrderedDict
@@ -174,13 +174,29 @@ def related_packages(self) -> list[Package]:
174174

175175
## Mappers
176176

177+
def _get_stately_resource_position(
178+
index: int, resources: List[Resource]) -> Optional[int]:
179+
"""
180+
Give state='deleted' resources null positions.
181+
182+
Required for con_package_resource_unique_position
183+
unique constraint when deleting resources.
184+
"""
185+
if resources[index].state != 'deleted':
186+
return index
187+
else:
188+
return None
189+
190+
177191
meta.registry.map_imperatively(Resource, resource_table, properties={
178192
'package': orm.relationship(
179193
Package,
180194
# all resources including deleted
181195
# formally package_resources_all
182196
backref=orm.backref('resources_all',
183-
collection_class=ordering_list('position'),
197+
collection_class=ordering_list(
198+
'position',
199+
ordering_func=_get_stately_resource_position),
184200
cascade='all, delete'
185201
),
186202
)

ckan/tests/logic/action/test_delete.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ def test_resource_delete(self):
3333
# It is still there but with state=deleted
3434
res_obj = model.Resource.get(resource["id"])
3535
assert res_obj.state == "deleted"
36+
assert res_obj.position is None
3637

3738
def test_resource_delete_for_delete(self):
3839

0 commit comments

Comments
 (0)