diff --git a/src/backend/core/migrations/0028_item_add_shortcut_target.py b/src/backend/core/migrations/0028_item_add_shortcut_target.py index f0f7defc..e0e765ec 100644 --- a/src/backend/core/migrations/0028_item_add_shortcut_target.py +++ b/src/backend/core/migrations/0028_item_add_shortcut_target.py @@ -14,7 +14,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='item', name='target', - field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='shortcut', to='core.item'), + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='shortcuts', to='core.item'), ), migrations.AlterField( model_name='item', @@ -25,4 +25,8 @@ class Migration(migrations.Migration): model_name='item', constraint=models.CheckConstraint(condition=models.Q(models.Q(('type', 'shortcut'), ('target__isnull', False)), models.Q(models.Q(('type', 'shortcut'), _negated=True), ('target__isnull', True)), _connector='OR'), name='check_target_only_on_shortcuts'), ), + migrations.AddConstraint( + model_name='item', + constraint=models.UniqueConstraint(fields=('target',), name='unique_shortcut_per_target'), + ), ] diff --git a/src/backend/core/models.py b/src/backend/core/models.py index 95a29839..f95e0e5b 100644 --- a/src/backend/core/models.py +++ b/src/backend/core/models.py @@ -1021,10 +1021,10 @@ class Item(TreeModel, BaseModel): ) mimetype = models.CharField(max_length=255, null=True, blank=True) is_restricted = models.BooleanField(default=False) - target = models.OneToOneField( + target = models.ForeignKey( "self", on_delete=models.CASCADE, - related_name="shortcut", + related_name="shortcuts", null=True, blank=True, ) @@ -1070,6 +1070,10 @@ class Item(TreeModel, BaseModel): ), name="check_target_only_on_shortcuts", ), + models.UniqueConstraint( + fields=["target"], + name="unique_shortcut_per_target", + ), ] indexes = [ GistIndex(fields=["path"]), @@ -1247,6 +1251,11 @@ class Item(TreeModel, BaseModel): return self._meta.model.objects.filter(path=str(self.path[:-1])).first() return None + @property + def shortcut(self): + """Return the shortcut targeting this item, if any.""" + return self._meta.model.objects.filter(target=self).first() + def invalidate_nb_accesses_cache(self): """ Invalidate the cache for number of accesses, including on affected descendants.