feat: bulk actions API

This commit is contained in:
Markos Gogoulos
2025-08-07 13:21:12 +03:00
committed by GitHub
parent de99d84c18
commit e790795bfd
69 changed files with 3879 additions and 2689 deletions

View File

@@ -11,7 +11,7 @@ from imagekit.models import ProcessedImageField
from imagekit.processors import ResizeToFill
import files.helpers as helpers
from files.models import Category, Media, Tag
from files.models import Category, Media, MediaPermission, Tag
from rbac.models import RBACGroup
@@ -123,7 +123,7 @@ class User(AbstractUser):
Get all categories related to RBAC groups the user belongs to
"""
rbac_groups = RBACGroup.objects.filter(memberships__user=self, memberships__role__in=["member", "contributor", "manager"])
categories = Category.objects.filter(rbac_groups__in=rbac_groups).distinct()
categories = Category.objects.prefetch_related("user").filter(rbac_groups__in=rbac_groups).distinct()
return categories
def has_member_access_to_category(self, category):
@@ -131,8 +131,63 @@ class User(AbstractUser):
return rbac_groups.exists()
def has_member_access_to_media(self, media):
rbac_groups = RBACGroup.objects.filter(memberships__user=self, memberships__role__in=["member", "contributor", "manager"], categories__in=media.category.all()).distinct()
return rbac_groups.exists()
# First check if user is the owner
if media.user == self:
return True
# Then check RBAC permissions
if getattr(settings, 'USE_RBAC', False):
rbac_groups = RBACGroup.objects.filter(memberships__user=self, memberships__role__in=["member", "contributor", "manager"], categories__in=media.category.all()).distinct()
if rbac_groups.exists():
return True
# Then check MediaShare permissions for any access
media_permission_exists = MediaPermission.objects.filter(
user=self,
media=media,
).exists()
return media_permission_exists
def has_contributor_access_to_media(self, media):
# First check if user is the owner
if media.user == self:
return True
# Then check RBAC permissions
if getattr(settings, 'USE_RBAC', False):
rbac_groups = RBACGroup.objects.filter(memberships__user=self, memberships__role__in=["contributor", "manager"], categories__in=media.category.all()).distinct()
if rbac_groups.exists():
return True
# Then check MediaShare permissions for editor or owner access
media_permission_exists = MediaPermission.objects.filter(
user=self,
media=media,
permission__in=["editor", "owner"],
).exists()
return media_permission_exists
def has_owner_access_to_media(self, media):
# First check if user is the owner
if media.user == self:
return True
# Then check RBAC permissions
if getattr(settings, 'USE_RBAC', False):
rbac_groups = RBACGroup.objects.filter(memberships__user=self, memberships__role__in=["manager"], categories__in=media.category.all()).distinct()
if rbac_groups.exists():
return True
# Then check MediaShare permissions for owner access
media_permission_exists = MediaPermission.objects.filter(
user=self,
media=media,
permission="owner",
).exists()
return media_permission_exists
def get_rbac_categories_as_contributor(self):
"""

View File

@@ -5,11 +5,8 @@ from . import views
urlpatterns = [
re_path(r"^user/(?P<username>[\w@._-]*)$", views.view_user, name="get_user"),
re_path(r"^user/(?P<username>[\w@._-]*)/$", views.view_user, name="get_user"),
re_path(
r"^user/(?P<username>[\w@.]*)/media$",
views.view_user_media,
name="get_user_media",
),
re_path(r"^user/(?P<username>[\w@._-]*)/shared_with_me", views.shared_with_me, name="shared_with_me"),
re_path(r"^user/(?P<username>[\w@._-]*)/shared_by_me", views.shared_by_me, name="shared_by_me"),
re_path(
r"^user/(?P<username>[\w@.]*)/playlists$",
views.view_user_playlists,
@@ -20,7 +17,7 @@ urlpatterns = [
views.view_user_about,
name="get_user_about",
),
re_path(r"^user/(?P<username>[\w@.]*)/edit$", views.edit_user, name="edit_user"),
re_path(r"^user/(?P<username>[\w@._-]*)/edit$", views.edit_user, name="edit_user"),
re_path(r"^channel/(?P<friendly_token>[\w]*)$", views.view_channel, name="view_channel"),
re_path(
r"^channel/(?P<friendly_token>[\w]*)/edit$",

View File

@@ -1,6 +1,7 @@
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.core.mail import EmailMessage
from django.db.models import Q
from django.http import HttpResponseRedirect
from django.shortcuts import render
from drf_yasg import openapi as openapi
@@ -47,17 +48,28 @@ def view_user(request, username):
return render(request, "cms/user.html", context)
def view_user_media(request, username):
def shared_with_me(request, username):
context = {}
user = get_user(username=username)
if not user:
return HttpResponseRedirect("/members")
if not user or (user != request.user):
return HttpResponseRedirect("/")
context["user"] = user
context["CAN_EDIT"] = True if ((user and user == request.user) or is_mediacms_manager(request.user)) else False
context["CAN_DELETE"] = True if is_mediacms_manager(request.user) else False
context["SHOW_CONTACT_FORM"] = True if (user.allow_contact or is_mediacms_editor(request.user)) else False
return render(request, "cms/user_media.html", context)
context["CAN_EDIT"] = True
context["CAN_DELETE"] = True
return render(request, "cms/user_shared_with_me.html", context)
def shared_by_me(request, username):
context = {}
user = get_user(username=username)
if not user or (user != request.user):
return HttpResponseRedirect("/")
context["user"] = user
context["CAN_EDIT"] = True
context["CAN_DELETE"] = True
return render(request, "cms/user_shared_by_me.html", context)
def view_user_playlists(request, username):
@@ -176,12 +188,17 @@ Sender email: %s\n
class UserList(APIView):
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
parser_classes = (JSONParser, MultiPartParser, FormParser, FileUploadParser)
def get_permissions(self):
if not settings.ALLOW_ANONYMOUS_USER_LISTING:
return [permissions.IsAuthenticated()]
return [permissions.IsAuthenticatedOrReadOnly()]
@swagger_auto_schema(
manual_parameters=[
openapi.Parameter(name='page', type=openapi.TYPE_INTEGER, in_=openapi.IN_QUERY, description='Page number'),
openapi.Parameter(name='name', type=openapi.TYPE_STRING, in_=openapi.IN_QUERY, description='Search by name or username'),
],
tags=['Users'],
operation_summary='List users',
@@ -191,9 +208,10 @@ class UserList(APIView):
pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
paginator = pagination_class()
users = User.objects.filter()
location = request.GET.get("location", "").strip()
if location:
users = users.filter(location=location)
name = request.GET.get("name", "").strip()
if name:
users = users.filter(Q(name__icontains=name) | Q(username__icontains=name))
page = paginator.paginate_queryset(users, request)