Source code for ferrosoft.apps.ferrobase.views.files

#  Copyright (c) 2025 Ferrosoft GmbH. All rights reserved.
from urllib.parse import urljoin

from django.contrib.postgres.aggregates import ArrayAgg
from django.utils.translation import gettext_lazy as _

from ferrosoft.apps.ferrobase.filter import StoredFileFilterSet
from ferrosoft.apps.ferrobase.models import (
    Directory,
    StoredFile,
    User,
    LocalUser,
    AccessSubject,
    AccessMode,
    FileAccessGrant,
)
from ferrosoft.apps.ferrobase.tables.models import StoredFileTable
from ferrosoft.apps.ferrobase.views.media import FORCE_DOWNLOAD_PARAM
from ferrosoft.apps.ferrobase.views.generic import ListView, TableView, UpdateView


[docs] class DirectoryRootView(ListView): """Top-level directory browser showing only root directories.""" activity_title = _("Files") help_topic = "files" model = Directory ordering = ["name"]
[docs] def get_queryset(self): """Restrict to directories without a parent (i.e. roots).""" return super().get_queryset().filter(parent_directory=None)
[docs] class DirectoryListingView(TableView): """List files within a directory filtered by per-user access grants.""" activity_title = _("Files") filterset_class = StoredFileFilterSet help_topic = "files" model = StoredFile ordering = ["name"] table_class = StoredFileTable
[docs] def get_queryset(self): """Union three access scopes (owner, organisation, public) for the directory. The UNION is built from three filtered querysets — owner files bypass access grants entirely, while organisation- and public-scoped files require an explicit READ grant. ``.distinct()`` collapses duplicates introduced by the UNION. """ directory_id = self.request.resolver_match.kwargs.get("directory") # Files are selected differently for each access subject and returned in # one big, beautiful UNION query. # # Owner files are a simple query, bypassing access grants since owner # must be able to manage permissions. owner_files = ( super() .get_queryset() .filter( directory_id=directory_id, owner=LocalUser.load(self.request.user), ) ) org_files = ( super() .get_queryset() .filter( directory_id=directory_id, owner__username__in=User.objects.filter( organization_id=self.request.user.organization_id ).values_list("username", flat=True), access_grants__subject=AccessSubject.ORGANIZATION, access_grants__mode=AccessMode.READ, ) ) public_files = ( super() .get_queryset() .filter( directory_id=directory_id, access_grants__subject=AccessSubject.PUBLIC, access_grants__mode=AccessMode.READ, ) ) return (owner_files | org_files | public_files).distinct()
[docs] class FileUpdateView(UpdateView): """Rename a file and edit its access grants matrix.""" activity_title = _("File") help_topic = "files" model = StoredFile fields = ["name"] template_name = "ferrobase/storedfile/update.html"
[docs] def get_context_data(self, **kwargs): """Build the ``{subject: {mode: bool}}`` access matrix and download URL.""" grants = { AccessSubject(grant["subject"]): [ AccessMode(mode) for mode in grant["modes"] ] for grant in self.object.access_grants.values("subject").annotate( modes=ArrayAgg("mode") ) } grants_matrix = {} for subject in AccessSubject: grants_matrix[subject] = { mode: mode in grants.get(subject, []) for mode in AccessMode } return super().get_context_data(**kwargs) | { "download_url": urljoin( self.object.get_absolute_url(), f"?{FORCE_DOWNLOAD_PARAM}=1" ), "grants": grants_matrix, "access_modes": AccessMode, }