First commit of claude's rework in django + vanillajs fronted
This commit is contained in:
0
apps/social/__init__.py
Normal file
0
apps/social/__init__.py
Normal file
33
apps/social/admin.py
Normal file
33
apps/social/admin.py
Normal file
@@ -0,0 +1,33 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from .models import BlogPost, Bug, Friendship, Message
|
||||
|
||||
|
||||
@admin.register(Message)
|
||||
class MessageAdmin(admin.ModelAdmin):
|
||||
list_display = ('sender', 'recipient', 'subject', 'sent_at', 'read_at')
|
||||
list_filter = ('sent_at',)
|
||||
search_fields = ('sender__email', 'recipient__email', 'subject')
|
||||
readonly_fields = ('sent_at', 'read_at')
|
||||
|
||||
|
||||
@admin.register(BlogPost)
|
||||
class BlogPostAdmin(admin.ModelAdmin):
|
||||
list_display = ('author', 'title', 'is_public', 'created_at')
|
||||
list_filter = ('is_public',)
|
||||
search_fields = ('author__email', 'title')
|
||||
|
||||
|
||||
@admin.register(Bug)
|
||||
class BugAdmin(admin.ModelAdmin):
|
||||
list_display = ('reporter', 'title', 'severity', 'status', 'created_at')
|
||||
list_filter = ('severity', 'status')
|
||||
search_fields = ('reporter__email', 'title')
|
||||
readonly_fields = ('created_at', 'updated_at', 'resolved_at')
|
||||
|
||||
|
||||
@admin.register(Friendship)
|
||||
class FriendshipAdmin(admin.ModelAdmin):
|
||||
list_display = ('from_user', 'to_user', 'status', 'created_at')
|
||||
list_filter = ('status',)
|
||||
search_fields = ('from_user__email', 'to_user__email')
|
||||
6
apps/social/apps.py
Normal file
6
apps/social/apps.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class SocialConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'apps.social'
|
||||
81
apps/social/migrations/0001_initial.py
Normal file
81
apps/social/migrations/0001_initial.py
Normal file
@@ -0,0 +1,81 @@
|
||||
# Generated by Django 4.2.16 on 2026-04-01 19:33
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Message',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('subject', models.CharField(max_length=200, verbose_name='subject')),
|
||||
('body', models.TextField(verbose_name='body')),
|
||||
('sent_at', models.DateTimeField(auto_now_add=True)),
|
||||
('read_at', models.DateTimeField(blank=True, null=True)),
|
||||
('deleted_by_sender', models.BooleanField(default=False)),
|
||||
('deleted_by_recipient', models.BooleanField(default=False)),
|
||||
('recipient', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='received_messages', to=settings.AUTH_USER_MODEL)),
|
||||
('sender', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sent_messages', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['-sent_at'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Bug',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.CharField(max_length=300, verbose_name='title')),
|
||||
('description', models.TextField(verbose_name='description')),
|
||||
('severity', models.CharField(choices=[('low', 'Low'), ('medium', 'Medium'), ('high', 'High'), ('critical', 'Critical')], default='medium', max_length=10, verbose_name='severity')),
|
||||
('status', models.CharField(choices=[('open', 'Open'), ('in_progress', 'In Progress'), ('resolved', 'Resolved'), ('closed', 'Closed')], default='open', max_length=15, verbose_name='status')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('resolved_at', models.DateTimeField(blank=True, null=True)),
|
||||
('reporter', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='reported_bugs', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['-created_at'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='BlogPost',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.CharField(max_length=300, verbose_name='title')),
|
||||
('body', models.TextField(verbose_name='body')),
|
||||
('is_public', models.BooleanField(default=True, verbose_name='public')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='blog_posts', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['-created_at'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Friendship',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('status', models.CharField(choices=[('pending', 'Pending'), ('accepted', 'Accepted'), ('blocked', 'Blocked')], default='pending', max_length=10, verbose_name='status')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('from_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='friendships_sent', to=settings.AUTH_USER_MODEL)),
|
||||
('to_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='friendships_received', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['-created_at'],
|
||||
'unique_together': {('from_user', 'to_user')},
|
||||
},
|
||||
),
|
||||
]
|
||||
0
apps/social/migrations/__init__.py
Normal file
0
apps/social/migrations/__init__.py
Normal file
111
apps/social/models.py
Normal file
111
apps/social/models.py
Normal file
@@ -0,0 +1,111 @@
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
# ── Message ───────────────────────────────────────────────────────────────────
|
||||
|
||||
class Message(models.Model):
|
||||
sender = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='sent_messages')
|
||||
recipient = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='received_messages')
|
||||
subject = models.CharField(_('subject'), max_length=200)
|
||||
body = models.TextField(_('body'))
|
||||
sent_at = models.DateTimeField(auto_now_add=True)
|
||||
read_at = models.DateTimeField(null=True, blank=True)
|
||||
deleted_by_sender = models.BooleanField(default=False)
|
||||
deleted_by_recipient = models.BooleanField(default=False)
|
||||
|
||||
class Meta:
|
||||
ordering = ['-sent_at']
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.sender} → {self.recipient}: {self.subject}'
|
||||
|
||||
|
||||
# ── BlogPost ──────────────────────────────────────────────────────────────────
|
||||
|
||||
class BlogPost(models.Model):
|
||||
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='blog_posts')
|
||||
title = models.CharField(_('title'), max_length=300)
|
||||
body = models.TextField(_('body'))
|
||||
is_public = models.BooleanField(_('public'), default=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ['-created_at']
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
|
||||
# ── Bug ───────────────────────────────────────────────────────────────────────
|
||||
|
||||
class BugSeverity(models.TextChoices):
|
||||
LOW = 'low', _('Low')
|
||||
MEDIUM = 'medium', _('Medium')
|
||||
HIGH = 'high', _('High')
|
||||
CRITICAL = 'critical', _('Critical')
|
||||
|
||||
|
||||
class BugStatus(models.TextChoices):
|
||||
OPEN = 'open', _('Open')
|
||||
IN_PROGRESS = 'in_progress', _('In Progress')
|
||||
RESOLVED = 'resolved', _('Resolved')
|
||||
CLOSED = 'closed', _('Closed')
|
||||
|
||||
|
||||
class Bug(models.Model):
|
||||
reporter = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL, on_delete=models.SET_NULL,
|
||||
null=True, related_name='reported_bugs',
|
||||
)
|
||||
title = models.CharField(_('title'), max_length=300)
|
||||
description = models.TextField(_('description'))
|
||||
severity = models.CharField(
|
||||
_('severity'), max_length=10,
|
||||
choices=BugSeverity.choices, default=BugSeverity.MEDIUM,
|
||||
)
|
||||
status = models.CharField(
|
||||
_('status'), max_length=15,
|
||||
choices=BugStatus.choices, default=BugStatus.OPEN,
|
||||
)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
resolved_at = models.DateTimeField(null=True, blank=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ['-created_at']
|
||||
|
||||
def __str__(self):
|
||||
return f'[{self.get_severity_display()}] {self.title}'
|
||||
|
||||
|
||||
# ── Friendship ────────────────────────────────────────────────────────────────
|
||||
|
||||
class FriendshipStatus(models.TextChoices):
|
||||
PENDING = 'pending', _('Pending')
|
||||
ACCEPTED = 'accepted', _('Accepted')
|
||||
BLOCKED = 'blocked', _('Blocked')
|
||||
|
||||
|
||||
class Friendship(models.Model):
|
||||
from_user = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='friendships_sent',
|
||||
)
|
||||
to_user = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='friendships_received',
|
||||
)
|
||||
status = models.CharField(
|
||||
_('status'), max_length=10,
|
||||
choices=FriendshipStatus.choices, default=FriendshipStatus.PENDING,
|
||||
)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
unique_together = [('from_user', 'to_user')]
|
||||
ordering = ['-created_at']
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.from_user} → {self.to_user} ({self.status})'
|
||||
105
apps/social/serializers.py
Normal file
105
apps/social/serializers.py
Normal file
@@ -0,0 +1,105 @@
|
||||
from django.contrib.auth import get_user_model
|
||||
from rest_framework import serializers
|
||||
|
||||
from .models import BlogPost, Bug, Friendship, Message
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
def _user_mini(user):
|
||||
if user is None:
|
||||
return None
|
||||
name = (f'{user.first_name} {user.last_name}'.strip()) or user.username
|
||||
return {'id': user.id, 'username': user.username, 'display_name': name}
|
||||
|
||||
|
||||
# ── Message ───────────────────────────────────────────────────────────────────
|
||||
|
||||
class MessageListSerializer(serializers.ModelSerializer):
|
||||
sender_detail = serializers.SerializerMethodField()
|
||||
recipient_detail = serializers.SerializerMethodField()
|
||||
|
||||
def get_sender_detail(self, obj): return _user_mini(obj.sender)
|
||||
def get_recipient_detail(self, obj): return _user_mini(obj.recipient)
|
||||
|
||||
class Meta:
|
||||
model = Message
|
||||
fields = ['id', 'sender_detail', 'recipient_detail', 'subject', 'sent_at', 'read_at']
|
||||
|
||||
|
||||
class MessageDetailSerializer(serializers.ModelSerializer):
|
||||
sender_detail = serializers.SerializerMethodField()
|
||||
recipient_detail = serializers.SerializerMethodField()
|
||||
|
||||
def get_sender_detail(self, obj): return _user_mini(obj.sender)
|
||||
def get_recipient_detail(self, obj): return _user_mini(obj.recipient)
|
||||
|
||||
class Meta:
|
||||
model = Message
|
||||
fields = ['id', 'sender_detail', 'recipient_detail', 'subject', 'body', 'sent_at', 'read_at']
|
||||
|
||||
|
||||
class MessageCreateSerializer(serializers.ModelSerializer):
|
||||
recipient = serializers.PrimaryKeyRelatedField(queryset=User.objects.all())
|
||||
|
||||
class Meta:
|
||||
model = Message
|
||||
fields = ['recipient', 'subject', 'body']
|
||||
|
||||
def validate_recipient(self, value):
|
||||
if value == self.context['request'].user:
|
||||
raise serializers.ValidationError('You cannot send a message to yourself.')
|
||||
return value
|
||||
|
||||
|
||||
# ── BlogPost ──────────────────────────────────────────────────────────────────
|
||||
|
||||
class BlogPostSerializer(serializers.ModelSerializer):
|
||||
author_detail = serializers.SerializerMethodField()
|
||||
|
||||
def get_author_detail(self, obj): return _user_mini(obj.author)
|
||||
|
||||
class Meta:
|
||||
model = BlogPost
|
||||
fields = ['id', 'author_detail', 'title', 'body', 'is_public', 'created_at', 'updated_at']
|
||||
read_only_fields = ['created_at', 'updated_at']
|
||||
|
||||
|
||||
# ── Bug ───────────────────────────────────────────────────────────────────────
|
||||
|
||||
class BugSerializer(serializers.ModelSerializer):
|
||||
reporter_detail = serializers.SerializerMethodField()
|
||||
|
||||
def get_reporter_detail(self, obj): return _user_mini(obj.reporter)
|
||||
|
||||
class Meta:
|
||||
model = Bug
|
||||
fields = [
|
||||
'id', 'reporter_detail', 'title', 'description',
|
||||
'severity', 'status', 'created_at', 'updated_at', 'resolved_at',
|
||||
]
|
||||
read_only_fields = ['status', 'resolved_at', 'created_at', 'updated_at']
|
||||
|
||||
|
||||
# ── Friendship ────────────────────────────────────────────────────────────────
|
||||
|
||||
class FriendshipSerializer(serializers.ModelSerializer):
|
||||
from_user_detail = serializers.SerializerMethodField()
|
||||
to_user_detail = serializers.SerializerMethodField()
|
||||
|
||||
def get_from_user_detail(self, obj): return _user_mini(obj.from_user)
|
||||
def get_to_user_detail(self, obj): return _user_mini(obj.to_user)
|
||||
|
||||
class Meta:
|
||||
model = Friendship
|
||||
fields = ['id', 'from_user_detail', 'to_user_detail', 'status', 'created_at']
|
||||
read_only_fields = ['status', 'created_at']
|
||||
|
||||
|
||||
class FriendRequestSerializer(serializers.Serializer):
|
||||
to_user = serializers.PrimaryKeyRelatedField(queryset=User.objects.all())
|
||||
|
||||
def validate_to_user(self, value):
|
||||
if value == self.context['request'].user:
|
||||
raise serializers.ValidationError('You cannot send a friend request to yourself.')
|
||||
return value
|
||||
14
apps/social/urls.py
Normal file
14
apps/social/urls.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from django.urls import path
|
||||
from rest_framework.routers import DefaultRouter
|
||||
|
||||
from .views import BlogPostViewSet, BugViewSet, FriendshipViewSet, MessageViewSet, member_search
|
||||
|
||||
router = DefaultRouter()
|
||||
router.register('social/messages', MessageViewSet, basename='message')
|
||||
router.register('social/blog', BlogPostViewSet, basename='blogpost')
|
||||
router.register('social/bugs', BugViewSet, basename='bug')
|
||||
router.register('social/friends', FriendshipViewSet, basename='friendship')
|
||||
|
||||
urlpatterns = [
|
||||
path('social/members/', member_search, name='member-search'),
|
||||
] + router.urls
|
||||
262
apps/social/views.py
Normal file
262
apps/social/views.py
Normal file
@@ -0,0 +1,262 @@
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.db.models import Q
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.utils import timezone
|
||||
from rest_framework import status, viewsets
|
||||
from rest_framework.decorators import action, api_view, permission_classes
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
|
||||
from .models import BlogPost, Bug, BugStatus, Friendship, FriendshipStatus, Message
|
||||
from .serializers import (
|
||||
BlogPostSerializer,
|
||||
BugSerializer,
|
||||
FriendRequestSerializer,
|
||||
FriendshipSerializer,
|
||||
MessageCreateSerializer,
|
||||
MessageDetailSerializer,
|
||||
MessageListSerializer,
|
||||
)
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
# ── Member search (for adding friends) ───────────────────────────────────────
|
||||
|
||||
@api_view(['GET'])
|
||||
@permission_classes([IsAuthenticated])
|
||||
def member_search(request):
|
||||
q = request.query_params.get('q', '').strip()
|
||||
if len(q) < 2:
|
||||
return Response([])
|
||||
users = (
|
||||
User.objects
|
||||
.exclude(pk=request.user.pk)
|
||||
.filter(
|
||||
Q(username__icontains=q) |
|
||||
Q(first_name__icontains=q) |
|
||||
Q(last_name__icontains=q)
|
||||
)[:20]
|
||||
)
|
||||
data = [
|
||||
{
|
||||
'id': u.id,
|
||||
'username': u.username,
|
||||
'display_name': (f'{u.first_name} {u.last_name}'.strip()) or u.username,
|
||||
}
|
||||
for u in users
|
||||
]
|
||||
return Response(data)
|
||||
|
||||
|
||||
# ── Messages ──────────────────────────────────────────────────────────────────
|
||||
|
||||
class MessageViewSet(viewsets.GenericViewSet):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def list(self, request):
|
||||
"""Inbox: messages received by the current user."""
|
||||
qs = (
|
||||
Message.objects
|
||||
.filter(recipient=request.user, deleted_by_recipient=False)
|
||||
.select_related('sender', 'recipient')
|
||||
.order_by('-sent_at')
|
||||
)
|
||||
return Response(MessageListSerializer(qs, many=True).data)
|
||||
|
||||
def create(self, request):
|
||||
ser = MessageCreateSerializer(data=request.data, context={'request': request})
|
||||
ser.is_valid(raise_exception=True)
|
||||
msg = ser.save(sender=request.user)
|
||||
return Response(MessageDetailSerializer(msg).data, status=status.HTTP_201_CREATED)
|
||||
|
||||
def retrieve(self, request, pk=None):
|
||||
user = request.user
|
||||
msg = get_object_or_404(
|
||||
Message,
|
||||
Q(sender=user, deleted_by_sender=False) |
|
||||
Q(recipient=user, deleted_by_recipient=False),
|
||||
pk=pk,
|
||||
)
|
||||
if msg.recipient == user and msg.read_at is None:
|
||||
msg.read_at = timezone.now()
|
||||
msg.save(update_fields=['read_at'])
|
||||
return Response(MessageDetailSerializer(msg).data)
|
||||
|
||||
def destroy(self, request, pk=None):
|
||||
user = request.user
|
||||
msg = get_object_or_404(
|
||||
Message,
|
||||
Q(sender=user) | Q(recipient=user),
|
||||
pk=pk,
|
||||
)
|
||||
if msg.sender == user:
|
||||
msg.deleted_by_sender = True
|
||||
if msg.recipient == user:
|
||||
msg.deleted_by_recipient = True
|
||||
msg.save(update_fields=['deleted_by_sender', 'deleted_by_recipient'])
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
@action(detail=False, methods=['get'], url_path='sent')
|
||||
def sent(self, request):
|
||||
qs = (
|
||||
Message.objects
|
||||
.filter(sender=request.user, deleted_by_sender=False)
|
||||
.select_related('sender', 'recipient')
|
||||
.order_by('-sent_at')
|
||||
)
|
||||
return Response(MessageListSerializer(qs, many=True).data)
|
||||
|
||||
@action(detail=False, methods=['get'], url_path='unread-count')
|
||||
def unread_count(self, request):
|
||||
count = Message.objects.filter(
|
||||
recipient=request.user, read_at__isnull=True, deleted_by_recipient=False
|
||||
).count()
|
||||
return Response({'unread': count})
|
||||
|
||||
|
||||
# ── BlogPost ──────────────────────────────────────────────────────────────────
|
||||
|
||||
class BlogPostViewSet(viewsets.ModelViewSet):
|
||||
permission_classes = [IsAuthenticated]
|
||||
serializer_class = BlogPostSerializer
|
||||
search_fields = ['title', 'body']
|
||||
ordering_fields = ['created_at']
|
||||
|
||||
def get_queryset(self):
|
||||
user = self.request.user
|
||||
return (
|
||||
BlogPost.objects
|
||||
.filter(Q(author=user) | Q(is_public=True))
|
||||
.select_related('author')
|
||||
)
|
||||
|
||||
def perform_create(self, serializer):
|
||||
serializer.save(author=self.request.user)
|
||||
|
||||
def check_write_permission(self, instance):
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
if instance.author != self.request.user and not self.request.user.is_staff:
|
||||
raise PermissionDenied
|
||||
|
||||
def perform_update(self, serializer):
|
||||
self.check_write_permission(self.get_object())
|
||||
serializer.save()
|
||||
|
||||
def perform_destroy(self, instance):
|
||||
self.check_write_permission(instance)
|
||||
instance.delete()
|
||||
|
||||
|
||||
# ── Bug ───────────────────────────────────────────────────────────────────────
|
||||
|
||||
class BugViewSet(viewsets.ModelViewSet):
|
||||
permission_classes = [IsAuthenticated]
|
||||
serializer_class = BugSerializer
|
||||
filterset_fields = ['severity', 'status']
|
||||
search_fields = ['title', 'description']
|
||||
ordering_fields = ['created_at', 'severity']
|
||||
|
||||
def get_queryset(self):
|
||||
user = self.request.user
|
||||
if user.is_staff:
|
||||
return Bug.objects.select_related('reporter').all()
|
||||
return Bug.objects.filter(reporter=user).select_related('reporter')
|
||||
|
||||
def perform_create(self, serializer):
|
||||
serializer.save(reporter=self.request.user)
|
||||
|
||||
@action(detail=True, methods=['post'], url_path='resolve')
|
||||
def resolve(self, request, pk=None):
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
if not request.user.is_staff:
|
||||
raise PermissionDenied
|
||||
bug = self.get_object()
|
||||
bug.status = BugStatus.RESOLVED
|
||||
bug.resolved_at = timezone.now()
|
||||
bug.save(update_fields=['status', 'resolved_at', 'updated_at'])
|
||||
return Response(BugSerializer(bug).data)
|
||||
|
||||
|
||||
# ── Friendship ────────────────────────────────────────────────────────────────
|
||||
|
||||
class FriendshipViewSet(viewsets.GenericViewSet):
|
||||
permission_classes = [IsAuthenticated]
|
||||
serializer_class = FriendshipSerializer
|
||||
|
||||
def list(self, request):
|
||||
"""Accepted friends."""
|
||||
user = request.user
|
||||
qs = (
|
||||
Friendship.objects
|
||||
.filter(Q(from_user=user) | Q(to_user=user), status=FriendshipStatus.ACCEPTED)
|
||||
.select_related('from_user', 'to_user')
|
||||
)
|
||||
return Response(FriendshipSerializer(qs, many=True).data)
|
||||
|
||||
def create(self, request):
|
||||
ser = FriendRequestSerializer(data=request.data, context={'request': request})
|
||||
ser.is_valid(raise_exception=True)
|
||||
to_user = ser.validated_data['to_user']
|
||||
user = request.user
|
||||
|
||||
existing = Friendship.objects.filter(
|
||||
Q(from_user=user, to_user=to_user) |
|
||||
Q(from_user=to_user, to_user=user)
|
||||
).first()
|
||||
if existing:
|
||||
return Response({'detail': 'A friendship or request already exists.'}, status=status.HTTP_409_CONFLICT)
|
||||
|
||||
friendship = Friendship.objects.create(from_user=user, to_user=to_user)
|
||||
return Response(FriendshipSerializer(friendship).data, status=status.HTTP_201_CREATED)
|
||||
|
||||
def destroy(self, request, pk=None):
|
||||
user = request.user
|
||||
friendship = get_object_or_404(
|
||||
Friendship,
|
||||
Q(from_user=user) | Q(to_user=user),
|
||||
pk=pk,
|
||||
)
|
||||
friendship.delete()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
@action(detail=False, methods=['get'], url_path='requests')
|
||||
def requests(self, request):
|
||||
"""Incoming pending requests."""
|
||||
qs = (
|
||||
Friendship.objects
|
||||
.filter(to_user=request.user, status=FriendshipStatus.PENDING)
|
||||
.select_related('from_user', 'to_user')
|
||||
)
|
||||
return Response(FriendshipSerializer(qs, many=True).data)
|
||||
|
||||
@action(detail=False, methods=['get'], url_path='sent-requests')
|
||||
def sent_requests(self, request):
|
||||
"""Outgoing pending requests."""
|
||||
qs = (
|
||||
Friendship.objects
|
||||
.filter(from_user=request.user, status=FriendshipStatus.PENDING)
|
||||
.select_related('from_user', 'to_user')
|
||||
)
|
||||
return Response(FriendshipSerializer(qs, many=True).data)
|
||||
|
||||
@action(detail=True, methods=['post'], url_path='accept')
|
||||
def accept(self, request, pk=None):
|
||||
friendship = get_object_or_404(
|
||||
Friendship, pk=pk, to_user=request.user, status=FriendshipStatus.PENDING
|
||||
)
|
||||
friendship.status = FriendshipStatus.ACCEPTED
|
||||
friendship.save(update_fields=['status', 'updated_at'])
|
||||
return Response(FriendshipSerializer(friendship).data)
|
||||
|
||||
@action(detail=True, methods=['post'], url_path='decline')
|
||||
def decline(self, request, pk=None):
|
||||
"""Decline an incoming request or cancel an outgoing one."""
|
||||
user = request.user
|
||||
friendship = get_object_or_404(
|
||||
Friendship,
|
||||
Q(from_user=user) | Q(to_user=user),
|
||||
pk=pk, status=FriendshipStatus.PENDING,
|
||||
)
|
||||
friendship.delete()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
Reference in New Issue
Block a user