Refine collection visibility and team selection

This commit is contained in:
rucki
2026-08-27 16:27:38 +02:00
parent 31d1828adb
commit 2bcd9d901a
5 changed files with 96 additions and 20 deletions
+42 -17
View File
@@ -173,7 +173,11 @@ def desired_item_scope(item, user, force_private=False):
def item_scope_fits_collections(item, visibility, team):
sections = item.collection_sections.select_related('collection') if item.pk else CollectionSection.objects.none()
return not any(section.collection.visibility != visibility or section.collection.team_id != (team.id if team else None) for section in sections)
team_id = team.id if team else None
return all(collection_scope_allows_item(
section.collection.visibility, section.collection.team_id, section.collection.owner_id,
visibility, team_id, item.owner_id,
) for section in sections)
def apply_team_from_tags(item, user, force_private=False):
@@ -546,10 +550,22 @@ def user_can_delete_collection(user, collection):
return user.is_authenticated and (collection.owner_id == user.id or (collection.team_id and TeamMembership.objects.filter(team=collection.team, user=user, role__in=[TeamMembership.Role.OWNER, TeamMembership.Role.ADMIN]).exists()))
def collection_scope_allows_item(collection_visibility, collection_team_id, collection_owner_id, item_visibility, item_team_id, item_owner_id):
# A collection may reference a note that is already visible to a wider audience.
if item_visibility == Item.Visibility.PUBLIC:
return True
if collection_visibility == Item.Visibility.PUBLIC:
return False
if collection_visibility == Item.Visibility.TEAM:
return item_visibility == Item.Visibility.TEAM and item_team_id == collection_team_id
return item_visibility == Item.Visibility.PRIVATE and item_owner_id == collection_owner_id
def collection_item_is_compatible(collection, item):
if collection.visibility == Item.Visibility.TEAM:
return item.visibility == Item.Visibility.TEAM and item.team_id == collection.team_id
return item.visibility == collection.visibility and item.team_id is None
return collection_scope_allows_item(
collection.visibility, collection.team_id, collection.owner_id,
item.visibility, item.team_id, item.owner_id,
)
def collection_target_from_form(form, user):
@@ -564,9 +580,13 @@ def collection_target_from_form(form, user):
def item_can_change_for_collection(user, item, collection):
if not user_can_edit_item(user, item):
return False
target_team_id = collection.team_id
for section in item.collection_sections.select_related('collection').exclude(collection=collection):
other = section.collection
if other.visibility != collection.visibility or other.team_id != collection.team_id:
if not collection_scope_allows_item(
other.visibility, other.team_id, other.owner_id,
collection.visibility, target_team_id, item.owner_id,
):
return False
return True
@@ -610,12 +630,18 @@ def add_collection_section(collection, item):
)
def editable_collection_notes(user):
return Item.objects.select_related('owner', 'team').prefetch_related('tags', 'attachments').filter(
kind=Item.Kind.NOTE,
).filter(Q(owner=user) | Q(team_id__in=user_team_ids(user))).distinct()
def collection_detail_context(request, collection, pending_item=None):
can_edit = user_can_edit_collection(request.user, collection)
q = request.GET.get('q', '').strip()
candidates = Item.objects.none()
if can_edit:
candidates = visible_items(request.user).filter(kind=Item.Kind.NOTE).exclude(collection_sections__collection=collection)
candidates = editable_collection_notes(request.user).exclude(collection_sections__collection=collection)
if q:
candidates = candidates.filter(Q(content__icontains=q) | Q(tags__name__icontains=q)).distinct()
candidates = candidates[:30]
@@ -640,7 +666,7 @@ def collection_list(request):
@login_required
def collection_create(request):
if request.method == 'POST':
form = CollectionForm(request.POST)
form = CollectionForm(request.POST, user=request.user)
if form.is_valid():
visibility, team = collection_target_from_form(form, request.user)
if visibility == Item.Visibility.TEAM and not team:
@@ -655,7 +681,7 @@ def collection_create(request):
messages.success(request, _('Collection created.'))
return redirect(collection)
else:
form = CollectionForm()
form = CollectionForm(user=request.user)
return render(request, 'core/collection_form.html', {'form': form, 'heading': _('Create collection')})
@@ -665,15 +691,14 @@ def collection_edit(request, pk):
if not user_can_edit_collection(request.user, collection):
return JsonResponse({'error': 'forbidden'}, status=403)
if request.method == 'POST':
form = CollectionForm(request.POST, instance=collection)
form = CollectionForm(request.POST, instance=collection, user=request.user)
if form.is_valid():
visibility, team = collection_target_from_form(form, request.user)
incompatible = collection.sections.exclude(item__visibility=visibility)
if team:
incompatible = incompatible | collection.sections.exclude(item__team=team)
else:
incompatible = incompatible | collection.sections.exclude(item__team__isnull=True)
if incompatible.exists():
incompatible = any(not collection_scope_allows_item(
visibility, team.id if team else None, collection.owner_id,
section.item.visibility, section.item.team_id, section.item.owner_id,
) for section in collection.sections.select_related('item'))
if incompatible:
form.add_error(None, _('The collection visibility cannot change while it contains incompatible notes.'))
elif visibility == Item.Visibility.TEAM and not team:
form.add_error('visibility', _('Select a team tag for a team collection.'))
@@ -686,7 +711,7 @@ def collection_edit(request, pk):
messages.success(request, _('Collection saved.'))
return redirect(collection)
else:
form = CollectionForm(instance=collection)
form = CollectionForm(instance=collection, user=request.user)
return render(request, 'core/collection_form.html', {'form': form, 'collection': collection, 'heading': _('Edit collection')})
@@ -708,7 +733,7 @@ def collection_manage(request, pk):
if request.method == 'POST':
action = request.POST.get('action')
if action in {'add', 'confirm_add'}:
item = get_object_or_404(visible_items(request.user), pk=request.POST.get('item_id'), kind=Item.Kind.NOTE)
item = get_object_or_404(editable_collection_notes(request.user), pk=request.POST.get('item_id'))
if collection_item_is_compatible(collection, item):
add_collection_section(collection, item)
return redirect(manage_url)