mirror of
https://github.com/suitenumerique/drive.git
synced 2026-09-22 01:25:11 +02:00
🐛(back) check title unicity when renaming an existing one
An item title can be updated. Check title unicity constraint on this case.
This commit is contained in:
@@ -255,6 +255,23 @@ class ItemSerializer(ListItemSerializer):
|
||||
def create(self, validated_data):
|
||||
raise NotImplementedError("Create method can not be used.")
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
"""Validate that the title is unique in the current path."""
|
||||
if (
|
||||
instance.title != validated_data.get("title")
|
||||
and instance.depth > 1
|
||||
and instance.is_item_title_existing(validated_data.get("title"))
|
||||
):
|
||||
raise serializers.ValidationError(
|
||||
{
|
||||
"title": _(
|
||||
"An item with this title already exists in the current path."
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
return super().update(instance, validated_data)
|
||||
|
||||
|
||||
class CreateItemSerializer(ItemSerializer):
|
||||
"""Serializer used to create a new item"""
|
||||
|
||||
+24
-10
@@ -417,6 +417,20 @@ class ItemQuerySet(TreeQuerySet):
|
||||
return self.filter(models.Q(link_reach=LinkReachChoices.PUBLIC))
|
||||
|
||||
|
||||
def _is_item_title_existing(queryset, title):
|
||||
"""Check if the title is unique in the same path."""
|
||||
return (
|
||||
queryset.filter(title=title)
|
||||
.filter(
|
||||
models.Q(
|
||||
models.Q(deleted_at__isnull=True)
|
||||
| models.Q(ancestors_deleted_at__isnull=True)
|
||||
)
|
||||
)
|
||||
.exists()
|
||||
)
|
||||
|
||||
|
||||
class ItemManager(TreeManager):
|
||||
"""Custom manager for Item model overriding create_child method."""
|
||||
|
||||
@@ -440,16 +454,9 @@ class ItemManager(TreeManager):
|
||||
if parent.type != ItemTypeChoices.FOLDER:
|
||||
raise ValidationError({"type": _("Only folders can have children.")})
|
||||
|
||||
if (
|
||||
self.children(parent.path)
|
||||
.filter(title=kwargs.get("title"))
|
||||
.filter(
|
||||
models.Q(
|
||||
models.Q(deleted_at__isnull=True)
|
||||
| models.Q(ancestors_deleted_at__isnull=True)
|
||||
)
|
||||
)
|
||||
.exists()
|
||||
if _is_item_title_existing(
|
||||
self.children(parent.path),
|
||||
kwargs.get("title"),
|
||||
):
|
||||
raise ValidationError(
|
||||
{"title": _("title already exists in this folder.")}
|
||||
@@ -603,6 +610,13 @@ class Item(TreeModel, BaseModel):
|
||||
"""Generate a unique cache key for each item."""
|
||||
return f"item_{self.id!s}_nb_accesses"
|
||||
|
||||
def is_item_title_existing(self, title):
|
||||
"""Check if the title is unique in the same path."""
|
||||
return _is_item_title_existing(
|
||||
self.siblings(),
|
||||
title,
|
||||
)
|
||||
|
||||
@property
|
||||
def nb_accesses(self):
|
||||
"""Calculate the number of accesses."""
|
||||
|
||||
@@ -351,3 +351,65 @@ def test_api_items_update_administrator_or_owner_of_another(via, mock_user_teams
|
||||
other_item.refresh_from_db()
|
||||
other_item_values = serializers.ItemSerializer(instance=other_item).data
|
||||
assert other_item_values == old_item_values
|
||||
|
||||
|
||||
def test_api_items_update_title_unique_in_current_path():
|
||||
"""
|
||||
The title of an item should be unique in the current path.
|
||||
"""
|
||||
user = factories.UserFactory()
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
root = factories.ItemFactory(
|
||||
title="item1", type=models.ItemTypeChoices.FOLDER, users=[user]
|
||||
)
|
||||
child = factories.ItemFactory(
|
||||
title="child1", type=models.ItemTypeChoices.FOLDER, parent=root, users=[user]
|
||||
)
|
||||
|
||||
factories.ItemFactory(
|
||||
title="child2", type=models.ItemTypeChoices.FOLDER, parent=root, users=[user]
|
||||
)
|
||||
|
||||
# update child1 to rename it to child2 should fails
|
||||
response = client.put(
|
||||
f"/api/v1.0/items/{child.id!s}/",
|
||||
{"title": "child2"},
|
||||
format="json",
|
||||
)
|
||||
assert response.status_code == 400
|
||||
assert response.json() == {
|
||||
"title": "An item with this title already exists in the current path."
|
||||
}
|
||||
|
||||
|
||||
def test_api_items_update_title_unique_in_current_path_soft_deleted():
|
||||
"""
|
||||
Reusing a title of a soft-deleted item should be allowed.
|
||||
"""
|
||||
user = factories.UserFactory()
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
root = factories.ItemFactory(
|
||||
title="item1", type=models.ItemTypeChoices.FOLDER, users=[user]
|
||||
)
|
||||
child = factories.ItemFactory(
|
||||
title="child1", type=models.ItemTypeChoices.FOLDER, parent=root, users=[user]
|
||||
)
|
||||
child.soft_delete()
|
||||
|
||||
child2 = factories.ItemFactory(
|
||||
title="child2", type=models.ItemTypeChoices.FOLDER, parent=root, users=[user]
|
||||
)
|
||||
|
||||
# update child 2 title using child 1 title is allowed
|
||||
response = client.put(
|
||||
f"/api/v1.0/items/{child2.id!s}/",
|
||||
{"title": "child1"},
|
||||
format="json",
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
Reference in New Issue
Block a user