gukgjzkgjhgjh
This commit is contained in:
0
backend/social/hubs/__init__.py
Normal file
0
backend/social/hubs/__init__.py
Normal file
3
backend/social/hubs/admin.py
Normal file
3
backend/social/hubs/admin.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
8
backend/social/hubs/apps.py
Normal file
8
backend/social/hubs/apps.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class HubsConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'social.hubs'
|
||||
|
||||
label = "hubs"
|
||||
0
backend/social/hubs/filters.py
Normal file
0
backend/social/hubs/filters.py
Normal file
73
backend/social/hubs/migrations/0001_initial.py
Normal file
73
backend/social/hubs/migrations/0001_initial.py
Normal file
@@ -0,0 +1,73 @@
|
||||
# Generated by Django 5.2.7 on 2026-04-19 21:51
|
||||
|
||||
import django.db.models.deletion
|
||||
import vontor_cz.custom_fields
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Hub',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('is_deleted', models.BooleanField(default=False)),
|
||||
('deleted_at', models.DateTimeField(blank=True, null=True)),
|
||||
('name', models.CharField(max_length=255, unique=True)),
|
||||
('description', models.TextField(blank=True, null=True)),
|
||||
('icon', vontor_cz.custom_fields.WebPImageField(blank=True, null=True, upload_to='hub_icons/')),
|
||||
('banner', vontor_cz.custom_fields.WebPImageField(blank=True, null=True, upload_to='hub_banners/')),
|
||||
('is_public', models.BooleanField(default=True)),
|
||||
('transfer_token', models.UUIDField(blank=True, null=True, unique=True)),
|
||||
('members', models.ManyToManyField(blank=True, related_name='hubs', to=settings.AUTH_USER_MODEL)),
|
||||
('owner', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='owned_hubs', to=settings.AUTH_USER_MODEL)),
|
||||
('transfer_to', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='pending_hub_transfers', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Tags',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('is_deleted', models.BooleanField(default=False)),
|
||||
('deleted_at', models.DateTimeField(blank=True, null=True)),
|
||||
('name', models.CharField(max_length=50)),
|
||||
('description', models.TextField(blank=True, null=True)),
|
||||
('color', models.CharField(default='#000000', max_length=7)),
|
||||
('hub', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tags', to='hubs.hub')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='HubPermission',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('is_deleted', models.BooleanField(default=False)),
|
||||
('deleted_at', models.DateTimeField(blank=True, null=True)),
|
||||
('changing_name', models.BooleanField(default=False)),
|
||||
('changing_description', models.BooleanField(default=False)),
|
||||
('changing_icon', models.BooleanField(default=False)),
|
||||
('changing_banner', models.BooleanField(default=False)),
|
||||
('managing_members', models.BooleanField(default=False)),
|
||||
('managing_posts', models.BooleanField(default=False)),
|
||||
('managing_chats', models.BooleanField(default=False)),
|
||||
('hub', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='moderators', to='hubs.hub')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'unique_together': {('hub', 'user')},
|
||||
},
|
||||
),
|
||||
]
|
||||
0
backend/social/hubs/migrations/__init__.py
Normal file
0
backend/social/hubs/migrations/__init__.py
Normal file
102
backend/social/hubs/models.py
Normal file
102
backend/social/hubs/models.py
Normal file
@@ -0,0 +1,102 @@
|
||||
from turtle import color
|
||||
import uuid
|
||||
from django.db import models
|
||||
from django.conf import settings
|
||||
from vontor_cz.custom_fields import WebPImageField
|
||||
from vontor_cz.models import SoftDeleteModel
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
# Create your models here.
|
||||
|
||||
|
||||
class Hub(SoftDeleteModel):
|
||||
AUTHOR_FIELD = 'owner'
|
||||
|
||||
name = models.CharField(max_length=255, unique=True)
|
||||
description = models.TextField(blank=True, null=True)
|
||||
|
||||
owner = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
related_name='owned_hubs'
|
||||
)
|
||||
|
||||
icon = WebPImageField(upload_to='hub_icons/', null=True, blank=True)
|
||||
banner = WebPImageField(upload_to='hub_banners/', null=True, blank=True)
|
||||
|
||||
members = models.ManyToManyField(
|
||||
settings.AUTH_USER_MODEL,
|
||||
related_name='hubs',
|
||||
blank=True
|
||||
)
|
||||
|
||||
is_public = models.BooleanField(default=True)
|
||||
|
||||
|
||||
|
||||
transfer_to = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name='pending_hub_transfers'
|
||||
)
|
||||
transfer_token = models.UUIDField(null=True, blank=True, unique=True)
|
||||
|
||||
def create_transfer(self, new_owner):
|
||||
self.transfer_to = new_owner
|
||||
self.transfer_token = uuid.uuid4()
|
||||
self.save(update_fields=['transfer_to', 'transfer_token'])
|
||||
|
||||
def verify_transfer(self, input_token, triggering_user):
|
||||
"""Requires uuid and triggering user must match the transfer_to field"""
|
||||
if self.transfer_token and str(self.transfer_token) == str(input_token) and self.transfer_to == triggering_user:
|
||||
self.owner = self.transfer_to
|
||||
|
||||
self.transfer_to = None
|
||||
self.transfer_token = None
|
||||
|
||||
self.save(update_fields=['owner', 'transfer_to', 'transfer_token'])
|
||||
return True
|
||||
raise ValidationError("Invalid transfer token or user does not match transfer_to field")
|
||||
|
||||
def cancel_transfer(self):
|
||||
self.transfer_to = None
|
||||
self.transfer_token = None
|
||||
self.save(update_fields=['transfer_to', 'transfer_token'])
|
||||
|
||||
def get_all_tags(self):
|
||||
return Tags.objects.filter(hub_id=self.id)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class HubPermission(SoftDeleteModel):
|
||||
hub = models.ForeignKey(Hub, on_delete=models.CASCADE, related_name='moderators')
|
||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
|
||||
|
||||
changing_name = models.BooleanField(default=False)
|
||||
changing_description = models.BooleanField(default=False)
|
||||
changing_icon = models.BooleanField(default=False)
|
||||
changing_banner = models.BooleanField(default=False)
|
||||
|
||||
managing_members = models.BooleanField(default=False)
|
||||
managing_posts = models.BooleanField(default=False)
|
||||
managing_chats = models.BooleanField(default=False)
|
||||
|
||||
class Meta:
|
||||
unique_together = ('hub', 'user')
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.user} moderates {self.hub}"
|
||||
|
||||
|
||||
class Tags(SoftDeleteModel):
|
||||
hub = models.ForeignKey(Hub, on_delete=models.CASCADE, related_name='tags')
|
||||
name = models.CharField(max_length=50)
|
||||
|
||||
description = models.TextField(blank=True, null=True)
|
||||
|
||||
color = models.CharField(max_length=7, default='#000000') # Hex color code
|
||||
53
backend/social/hubs/permissions.py
Normal file
53
backend/social/hubs/permissions.py
Normal file
@@ -0,0 +1,53 @@
|
||||
from rest_framework.permissions import IsAuthenticated, SAFE_METHODS
|
||||
|
||||
|
||||
class CanEditHub(IsAuthenticated):
|
||||
"""
|
||||
Hub object-level permission.
|
||||
- View-level: must be authenticated (inherited).
|
||||
- Object-level unsafe: hub owner, superuser, or any moderator
|
||||
(field-level restrictions enforced in HubSerializer).
|
||||
"""
|
||||
|
||||
def has_object_permission(self, request, view, obj):
|
||||
if request.method in SAFE_METHODS:
|
||||
return True
|
||||
|
||||
user = request.user
|
||||
if obj.owner == user or user.is_superuser:
|
||||
return True
|
||||
|
||||
return obj.moderators.filter(user=user).exists()
|
||||
|
||||
|
||||
class IsHubOwnerOrSuperuser(IsAuthenticated):
|
||||
"""
|
||||
For objects with a .hub FK (e.g. HubPermission).
|
||||
- View-level: must be authenticated (inherited).
|
||||
- Object-level unsafe: hub owner or superuser only.
|
||||
"""
|
||||
|
||||
def has_object_permission(self, request, view, obj):
|
||||
if request.method in SAFE_METHODS:
|
||||
return True
|
||||
|
||||
return request.user.is_superuser or obj.hub.owner == request.user
|
||||
|
||||
|
||||
class CanManageHubTags(IsAuthenticated):
|
||||
"""
|
||||
For Tags (navigates via obj.hub).
|
||||
- View-level: must be authenticated (inherited).
|
||||
- Object-level unsafe: hub owner, superuser, or moderator with managing_posts=True.
|
||||
"""
|
||||
|
||||
def has_object_permission(self, request, view, obj):
|
||||
if request.method in SAFE_METHODS:
|
||||
return True
|
||||
|
||||
user = request.user
|
||||
hub = obj.hub
|
||||
if user.is_superuser or hub.owner == user:
|
||||
return True
|
||||
|
||||
return hub.moderators.filter(user=user, managing_posts=True).exists()
|
||||
81
backend/social/hubs/serializers.py
Normal file
81
backend/social/hubs/serializers.py
Normal file
@@ -0,0 +1,81 @@
|
||||
from rest_framework import serializers
|
||||
from .models import Hub, HubPermission, Tags
|
||||
|
||||
|
||||
# Maps Hub fields -> the HubPermission flag that allows changing them
|
||||
FIELD_PERMISSION_MAP = {
|
||||
'name': 'changing_name',
|
||||
'description': 'changing_description',
|
||||
'icon': 'changing_icon',
|
||||
'banner': 'changing_banner',
|
||||
'members': 'managing_members',
|
||||
}
|
||||
|
||||
|
||||
class TransferInitSerializer(serializers.Serializer):
|
||||
user_id = serializers.IntegerField(help_text="PK of the user to transfer ownership to.")
|
||||
|
||||
|
||||
class TransferVerifySerializer(serializers.Serializer):
|
||||
token = serializers.UUIDField(help_text="Transfer token sent to the new owner.")
|
||||
|
||||
|
||||
class TagsSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Tags
|
||||
fields = ['id', 'name', 'description', 'color']
|
||||
|
||||
|
||||
class HubPermissionSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = HubPermission
|
||||
fields = [
|
||||
'id', 'user',
|
||||
'changing_name', 'changing_description', 'changing_icon', 'changing_banner',
|
||||
'managing_members', 'managing_posts', 'managing_chats',
|
||||
]
|
||||
|
||||
|
||||
class HubSerializer(serializers.ModelSerializer):
|
||||
tags = TagsSerializer(many=True, read_only=True)
|
||||
moderators = HubPermissionSerializer(many=True, read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Hub
|
||||
fields = [
|
||||
'id', 'name', 'description', 'owner',
|
||||
'icon', 'banner', 'members', 'is_public',
|
||||
'tags', 'moderators',
|
||||
]
|
||||
read_only_fields = ['owner']
|
||||
|
||||
def validate(self, attrs):
|
||||
hub = self.instance # None on create — no restrictions needed
|
||||
if hub is None:
|
||||
return attrs
|
||||
|
||||
request = self.context.get('request')
|
||||
if request is None:
|
||||
return attrs
|
||||
|
||||
user = request.user
|
||||
|
||||
# Owner and superuser bypass field-level checks
|
||||
if hub.owner == user or user.is_superuser:
|
||||
return attrs
|
||||
|
||||
# Moderator: enforce per-field permission flags
|
||||
try:
|
||||
perm = hub.moderators.get(user=user)
|
||||
|
||||
except HubPermission.DoesNotExist:
|
||||
raise serializers.ValidationError('You do not have permission to edit this hub.')
|
||||
|
||||
for field in attrs:
|
||||
flag = FIELD_PERMISSION_MAP.get(field)
|
||||
if flag and not getattr(perm, flag, False):
|
||||
raise serializers.ValidationError(
|
||||
{field: 'You do not have permission to change this field.'}
|
||||
)
|
||||
|
||||
return attrs
|
||||
3
backend/social/hubs/tests.py
Normal file
3
backend/social/hubs/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
9
backend/social/hubs/urls.py
Normal file
9
backend/social/hubs/urls.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from .views import HubViewSet, HubPermissionViewSet, TagsViewSet
|
||||
|
||||
router = DefaultRouter()
|
||||
router.register('', HubViewSet, basename='hub')
|
||||
router.register('moderators', HubPermissionViewSet, basename='hub-moderator')
|
||||
router.register('tags', TagsViewSet, basename='hub-tag')
|
||||
|
||||
urlpatterns = router.urls
|
||||
294
backend/social/hubs/views.py
Normal file
294
backend/social/hubs/views.py
Normal file
@@ -0,0 +1,294 @@
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import get_user_model
|
||||
from rest_framework import status, viewsets
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
from rest_framework.response import Response
|
||||
from drf_spectacular.utils import extend_schema, extend_schema_view
|
||||
|
||||
from .models import Hub, HubPermission, Tags
|
||||
from .permissions import CanEditHub, CanManageHubTags, IsHubOwnerOrSuperuser
|
||||
from .serializers import HubPermissionSerializer, HubSerializer, TagsSerializer, TransferInitSerializer, TransferVerifySerializer
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Hub ViewSet
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@extend_schema_view(
|
||||
list=extend_schema(
|
||||
tags=["hubs"],
|
||||
summary="List hubs",
|
||||
description=(
|
||||
"Returns all public hubs. Authenticated users also see hubs they are members of. "
|
||||
"Admins see all hubs regardless of visibility."
|
||||
),
|
||||
),
|
||||
retrieve=extend_schema(
|
||||
tags=["hubs"],
|
||||
summary="Retrieve a hub",
|
||||
),
|
||||
create=extend_schema(
|
||||
tags=["hubs"],
|
||||
summary="Create a hub",
|
||||
description="Creates a new hub. The requesting user is automatically set as the owner.",
|
||||
),
|
||||
update=extend_schema(
|
||||
tags=["hubs"],
|
||||
summary="Replace a hub",
|
||||
description=(
|
||||
"Full update. Restricted to the owner, site admin, or moderators "
|
||||
"(per-field permission flags are enforced)."
|
||||
),
|
||||
),
|
||||
partial_update=extend_schema(
|
||||
tags=["hubs"],
|
||||
summary="Update a hub",
|
||||
description=(
|
||||
"Partial update. Restricted to the owner, site admin, or moderators "
|
||||
"(per-field permission flags are enforced)."
|
||||
),
|
||||
),
|
||||
destroy=extend_schema(
|
||||
tags=["hubs"],
|
||||
summary="Delete a hub",
|
||||
description="Soft-deletes the hub. Owner or admin only.",
|
||||
),
|
||||
)
|
||||
class HubViewSet(viewsets.ModelViewSet):
|
||||
serializer_class = HubSerializer
|
||||
permission_classes = [CanEditHub]
|
||||
filterset_fields = ['is_public', 'owner']
|
||||
search_fields = ['name', 'description']
|
||||
ordering_fields = ['name']
|
||||
ordering = ['name']
|
||||
|
||||
def get_queryset(self):
|
||||
user = self.request.user
|
||||
if user.is_superuser:
|
||||
return Hub.objects.all()
|
||||
return (
|
||||
Hub.objects.filter(is_public=True) | Hub.objects.filter(members=user)
|
||||
).distinct()
|
||||
|
||||
def perform_create(self, serializer):
|
||||
serializer.save(owner=self.request.user)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Membership actions
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
@extend_schema(
|
||||
tags=["hubs"],
|
||||
summary="Join a hub",
|
||||
description="Adds the authenticated user as a member. Private hubs reject this request.",
|
||||
request=None,
|
||||
responses={200: HubSerializer},
|
||||
)
|
||||
@action(detail=True, methods=['post'])
|
||||
def join(self, request, pk=None):
|
||||
hub = self.get_object()
|
||||
if not hub.is_public and not (hub.owner == request.user or request.user.is_superuser):
|
||||
return Response(
|
||||
{'detail': 'This hub is private.'},
|
||||
status=status.HTTP_403_FORBIDDEN,
|
||||
)
|
||||
hub.members.add(request.user)
|
||||
return Response(HubSerializer(hub, context={'request': request}).data)
|
||||
|
||||
@extend_schema(
|
||||
tags=["hubs"],
|
||||
summary="Leave a hub",
|
||||
description="Removes the authenticated user from the hub's members.",
|
||||
request=None,
|
||||
responses={204: None},
|
||||
)
|
||||
@action(detail=True, methods=['post'])
|
||||
def leave(self, request, pk=None):
|
||||
hub = self.get_object()
|
||||
hub.members.remove(request.user)
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Ownership transfer actions
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
@extend_schema(
|
||||
tags=["hubs"],
|
||||
summary="Initiate ownership transfer",
|
||||
description=(
|
||||
"Generates a transfer token and records the intended new owner. "
|
||||
"Only the current hub owner can initiate. The recipient must call "
|
||||
"`transfer/verify` with the token to complete the transfer."
|
||||
),
|
||||
request=TransferInitSerializer,
|
||||
responses={200: TransferInitSerializer},
|
||||
)
|
||||
@action(detail=True, methods=['post'], url_path='transfer/initiate')
|
||||
def initiate_transfer(self, request, pk=None):
|
||||
hub = self.get_object()
|
||||
if hub.owner != request.user:
|
||||
return Response(
|
||||
{'detail': 'Only the hub owner can initiate a transfer.'},
|
||||
status=status.HTTP_403_FORBIDDEN,
|
||||
)
|
||||
ser = TransferInitSerializer(data=request.data)
|
||||
ser.is_valid(raise_exception=True)
|
||||
|
||||
User = get_user_model()
|
||||
try:
|
||||
new_owner = User.objects.get(pk=ser.validated_data['user_id'])
|
||||
except User.DoesNotExist:
|
||||
return Response({'detail': 'User not found.'}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
hub.create_transfer(new_owner)
|
||||
transfer_url = f"{settings.FRONTEND_URL}/hubs/{hub.pk}/transfer/verify?token={hub.transfer_token}"
|
||||
return Response({'detail': f'Transfer initiated to {new_owner}.', 'transfer_url': transfer_url})
|
||||
|
||||
@extend_schema(
|
||||
tags=["hubs"],
|
||||
summary="Verify ownership transfer",
|
||||
description=(
|
||||
"Completes the transfer when the intended new owner supplies the correct token. "
|
||||
"Must be called by the transfer recipient."
|
||||
),
|
||||
request=TransferVerifySerializer,
|
||||
responses={200: HubSerializer},
|
||||
)
|
||||
@action(detail=True, methods=['post'], url_path='transfer/verify')
|
||||
def verify_transfer(self, request, pk=None):
|
||||
hub = self.get_object()
|
||||
ser = TransferVerifySerializer(data=request.data)
|
||||
ser.is_valid(raise_exception=True)
|
||||
try:
|
||||
hub.verify_transfer(ser.validated_data['token'], request.user)
|
||||
except Exception as exc:
|
||||
return Response({'detail': str(exc)}, status=status.HTTP_400_BAD_REQUEST)
|
||||
return Response(HubSerializer(hub, context={'request': request}).data)
|
||||
|
||||
@extend_schema(
|
||||
tags=["hubs"],
|
||||
summary="Cancel ownership transfer",
|
||||
description="Cancels a pending transfer, clearing the token and recipient. Owner or admin only.",
|
||||
request=None,
|
||||
responses={200: None},
|
||||
)
|
||||
@action(detail=True, methods=['post'], url_path='transfer/cancel')
|
||||
def cancel_transfer(self, request, pk=None):
|
||||
hub = self.get_object()
|
||||
if not (hub.owner == request.user or request.user.is_superuser):
|
||||
raise PermissionDenied('Only the hub owner or superuser can cancel a transfer.')
|
||||
hub.cancel_transfer()
|
||||
return Response({'detail': 'Transfer cancelled.'})
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Hub Moderator (HubPermission) ViewSet
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@extend_schema_view(
|
||||
list=extend_schema(
|
||||
tags=["hubs"],
|
||||
summary="List hub moderators",
|
||||
description="Returns all moderators and their permission flags for a given hub. Pass `hub` as a query param.",
|
||||
),
|
||||
retrieve=extend_schema(
|
||||
tags=["hubs"],
|
||||
summary="Retrieve a hub moderator",
|
||||
),
|
||||
create=extend_schema(
|
||||
tags=["hubs"],
|
||||
summary="Add a hub moderator",
|
||||
description="Grants a user moderator permissions on the hub. Owner or admin only.",
|
||||
),
|
||||
partial_update=extend_schema(
|
||||
tags=["hubs"],
|
||||
summary="Update moderator permissions",
|
||||
description="Updates one or more permission flags for a moderator. Owner or admin only.",
|
||||
),
|
||||
update=extend_schema(
|
||||
tags=["hubs"],
|
||||
summary="Replace moderator permissions",
|
||||
description="Replaces all permission flags for a moderator. Owner or admin only.",
|
||||
),
|
||||
destroy=extend_schema(
|
||||
tags=["hubs"],
|
||||
summary="Remove a hub moderator",
|
||||
description="Revokes all moderator permissions from a user. Owner or admin only.",
|
||||
),
|
||||
)
|
||||
class HubPermissionViewSet(viewsets.ModelViewSet):
|
||||
serializer_class = HubPermissionSerializer
|
||||
permission_classes = [IsHubOwnerOrSuperuser]
|
||||
filterset_fields = ['user', 'changing_name', 'changing_description', 'changing_icon', 'changing_banner', 'managing_members', 'managing_posts', 'managing_chats']
|
||||
|
||||
def _get_hub(self):
|
||||
hub_id = self.kwargs.get('hub_pk') or self.request.query_params.get('hub')
|
||||
return Hub.objects.get(pk=hub_id)
|
||||
|
||||
def get_queryset(self):
|
||||
return HubPermission.objects.filter(hub=self._get_hub())
|
||||
|
||||
def perform_create(self, serializer):
|
||||
hub = self._get_hub()
|
||||
if not (hub.owner == self.request.user or self.request.user.is_superuser):
|
||||
raise PermissionDenied('Only the hub owner or superuser can add moderators.')
|
||||
serializer.save(hub=hub)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tags ViewSet
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@extend_schema_view(
|
||||
list=extend_schema(
|
||||
tags=["hubs"],
|
||||
summary="List hub tags",
|
||||
description="Returns all tags for a given hub. Pass `hub` as a query param.",
|
||||
),
|
||||
retrieve=extend_schema(
|
||||
tags=["hubs"],
|
||||
summary="Retrieve a hub tag",
|
||||
),
|
||||
create=extend_schema(
|
||||
tags=["hubs"],
|
||||
summary="Create a hub tag",
|
||||
description="Adds a tag to the hub. Owner, site admin, or moderator with `managing_posts`.",
|
||||
),
|
||||
partial_update=extend_schema(
|
||||
tags=["hubs"],
|
||||
summary="Update a hub tag",
|
||||
description="Updates a tag on the hub. Owner, site admin, or moderator with `managing_posts`.",
|
||||
),
|
||||
update=extend_schema(
|
||||
tags=["hubs"],
|
||||
summary="Replace a hub tag",
|
||||
description="Replaces a tag on the hub. Owner, site admin, or moderator with `managing_posts`.",
|
||||
),
|
||||
destroy=extend_schema(
|
||||
tags=["hubs"],
|
||||
summary="Delete a hub tag",
|
||||
description="Removes a tag from the hub. Owner, site admin, or moderator with `managing_posts`.",
|
||||
),
|
||||
)
|
||||
class TagsViewSet(viewsets.ModelViewSet):
|
||||
serializer_class = TagsSerializer
|
||||
permission_classes = [CanManageHubTags]
|
||||
search_fields = ['name', 'description']
|
||||
ordering_fields = ['name']
|
||||
ordering = ['name']
|
||||
|
||||
def _get_hub(self):
|
||||
hub_id = self.kwargs.get('hub_pk') or self.request.query_params.get('hub')
|
||||
return Hub.objects.get(pk=hub_id)
|
||||
|
||||
def get_queryset(self):
|
||||
return Tags.objects.filter(hub=self._get_hub())
|
||||
|
||||
def perform_create(self, serializer):
|
||||
hub = self._get_hub()
|
||||
user = self.request.user
|
||||
if not (user.is_superuser or hub.owner == user or hub.moderators.filter(user=user, managing_posts=True).exists()):
|
||||
raise PermissionDenied('Only the hub owner, superuser, or moderator with managing_posts can create tags.')
|
||||
serializer.save(hub=hub)
|
||||
|
||||
Reference in New Issue
Block a user