GooDrive/backend/drive/views_items.py
Louis Mazin 97a25e4df8 nice
2026-10-13 21:15:53 +02:00

150 lines
6.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Drive navigation, folder management, metadata and trash endpoints."""
from pathlib import Path
from django.conf import settings
from django.core.exceptions import ValidationError
from django.db import transaction
from django.db.models import Q, Sum, Count
from django.utils import timezone
from rest_framework.decorators import api_view
from rest_framework.response import Response
from .models import DriveItem
from .services import error, item_info, get_item, chain, readable, writable, valid_name, target_parent, descendant_files
@api_view(['GET'])
def me(request):
return Response({'id':request.user.pk,'username':request.user.username,
'is_admin':request.user.is_staff or request.user.is_superuser,
'first_name':request.user.first_name})
@api_view(['GET'])
def listing(request):
user=request.user
view=request.query_params.get('view','files')
q=request.query_params.get('q','').strip()
parent_id=request.query_params.get('parent')
parent=None
if parent_id:
parent=get_item(parent_id)
if not parent.is_folder or not readable(parent,user):
return error('Dossier inaccessible.',403)
if view=='trash':
nodes=DriveItem.objects.filter(owner=user,trashed_at__isnull=False).select_related('owner')
elif view=='shared':
nodes=DriveItem.objects.filter(parent__isnull=True,is_shared=True,trashed_at__isnull=True).select_related('owner')
if not user.is_staff:
nodes=nodes.exclude(owner=user)
elif view=='starred':
nodes=DriveItem.objects.filter(owner=user,is_starred=True,trashed_at__isnull=True).select_related('owner','parent')
elif view=='recent':
nodes=DriveItem.objects.filter(owner=user,is_folder=False,trashed_at__isnull=True).select_related('owner','parent').order_by('-updated_at')
elif q:
nodes=DriveItem.objects.filter(owner=user,trashed_at__isnull=True,name__icontains=q).select_related('owner','parent')
elif parent:
nodes=DriveItem.objects.filter(parent=parent,trashed_at__isnull=True).select_related('owner','parent')
else:
nodes=DriveItem.objects.filter(parent__isnull=True,trashed_at__isnull=True).filter(
Q(owner=user)|Q(is_shared=True)).select_related('owner','parent')
items=[]
for n in nodes[:500]:
if view=='trash' or readable(n,user):
items.append(item_info(n))
trail=[]
if parent:
for n in reversed(chain(parent)):
if readable(n,user):
trail.append({'id':str(n.id),'name':n.name})
return Response({'items':items,'breadcrumb':trail,'limited':len(items)>=500})
@api_view(['GET'])
def stats(request):
user=request.user
result=DriveItem.objects.filter(owner=user,trashed_at__isnull=True,is_folder=False).aggregate(
count=Count('id'),total=Sum('size'))
return Response({'files':result['count'] or 0,'bytes':result['total'] or 0,
'folders':DriveItem.objects.filter(owner=user,is_folder=True,trashed_at__isnull=True).count(),
'shared':DriveItem.objects.filter(owner=user,parent__isnull=True,is_shared=True,trashed_at__isnull=True).count()})
@api_view(['POST'])
def create_folder(request):
try:
name=valid_name(request.data.get('name'))
parent=target_parent(request.data.get('parent'),request.user,request.user.pk)
except ValueError as ex: return error(str(ex))
except PermissionError as ex: return error(str(ex),403)
folder=DriveItem(owner=request.user,parent=parent,name=name,is_folder=True)
folder.full_clean()
folder.save()
return Response(item_info(folder),status=201)
@api_view(['PATCH'])
def update_item(request,item_id):
item=get_item(item_id)
if not readable(item,request.user) or not writable(item,request.user):
return error('Modification interdite.',403)
data=request.data
if 'name' in data:
try:item.name=valid_name(data['name'])
except ValueError as ex:return error(str(ex))
if 'is_starred' in data:item.is_starred=bool(data['is_starred'])
if 'is_shared' in data:
if item.parent_id:return error('Seuls les éléments à la racine peuvent être partagés.')
item.is_shared=bool(data['is_shared'])
if 'parent' in data:
try:
new_parent=target_parent(data['parent'],request.user,item.owner_id)
except ValueError as ex:return error(str(ex))
except PermissionError as ex:return error(str(ex),403)
if new_parent:
if new_parent.id==item.id or item.id in [n.id for n in chain(new_parent)]:
return error('Déplacement impossible dans un sous-dossier de cet élément.')
item.parent=new_parent
if new_parent:item.is_shared=False
try:item.full_clean()
except ValidationError as ex:return error(str(ex))
item.save()
return Response(item_info(item))
@api_view(['POST'])
def trash_item(request,item_id):
item=get_item(item_id)
if not readable(item,request.user) or not writable(item,request.user):return error('Accès refusé.',403)
if item.trashed_at:return error('Élément déjà dans la corbeille.')
item.trashed_at=timezone.now();item.save(update_fields=['trashed_at','updated_at'])
return Response({'ok':True})
@api_view(['POST'])
def restore_item(request,item_id):
item=get_item(item_id)
if not writable(item,request.user):return error('Accès refusé.',403)
if not item.trashed_at:return error('Cet élément n’est pas dans la corbeille.')
# An item whose parent was separately trashed is restored to root.
if item.parent and any(n.trashed_at for n in chain(item.parent)):
item.parent=None
item.trashed_at=None;item.save()
return Response(item_info(item))
@api_view(['DELETE'])
def delete_item(request,item_id):
item=get_item(item_id)
if not writable(item,request.user):return error('Accès refusé.',403)
if not item.trashed_at:return error('Déplace d’abord cet élément dans la corbeille.')
files=descendant_files(item)
with transaction.atomic():
item.delete()
def remove_files():
for key in files:
path=Path(settings.DRIVE_STORAGE_ROOT)/key[:2]/key
path.unlink(missing_ok=True)
transaction.on_commit(remove_files)
return Response(status=204)