/
Codename-Nik
/
WebApp
Обзор
Документация
Войти
/
Codename-Nik
/
WebApp
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
backend/api/views.py
389 строк
15 KB
Nikolay Pokhodnya
first commit
03 ноя 2025, 19:47
03 ноя 2025, 19:47
a9fbdee
Код
Авторство
О чём код?
from django.conf import settings from django.shortcuts import render from django.utils import timezone from django.core.mail import send_mail from django.template.loader import render_to_string from rest_framework import generics, permissions, status, filters from rest_framework.decorators import api_view, action, permission_classes from rest_framework.response import Response from rest_framework.reverse import reverse from rest_framework.viewsets import ModelViewSet from django_filters.rest_framework import DjangoFilterBackend from django.db.models import Q from django.contrib.auth import get_user_model from .models import Invitation, Task, Project, Comment from .serializers import ( UserSerializer, UserRegistrationSerializer, UserProfileUpdateSerializer, UserListSerializer, TaskListSerializer, TaskDetailSerializer, TaskCreateSerializer, ProjectSerializer, ProjectCreateSerializer, CommentSerializer, InvitationSerializer, InvitationCreateSerializer, InvitationAcceptSerializer, InvitationResponseSerializer ) User = get_user_model() # Create your views here. class UserRegistrationView(generics.CreateAPIView): queryset = User.objects.all() serializer_class = UserRegistrationSerializer permission_classes = [permissions.AllowAny] class UserProfileView(generics.RetrieveUpdateAPIView): serializer_class = UserProfileUpdateSerializer permission_classes = [permissions.IsAuthenticated] def get_object(self): return self.request.user class UserListView(generics.ListAPIView): serializer_class = UserListSerializer permission_classes = [permissions.IsAuthenticated] filter_backends = [filters.SearchFilter] search_fields = ['username', 'first_name', 'last_name', 'email'] def get_queryset(self): return User.objects.exclude(id=self.request.user.id) class ProjectViewSet(ModelViewSet): permission_classes = [permissions.IsAuthenticated] def get_serializer_class(self): if self.action == 'create': return ProjectCreateSerializer return ProjectSerializer def get_queryset(self): return Project.objects.filter( Q(owner=self.request.user) | Q(members=self.request.user) ).distinct() def perform_create(self, serializer): serializer.save(owner=self.request.user) @action(detail=True, methods=['post']) def add_member(self, request, pk=None): project = self.get_object() user_id = request.data.get('user_id') if not user_id: return Response({'error': 'user_id is required'}, status=status.HTTP_400_BAD_REQUEST) try: user = User.objects.get(id=user_id) if project.members.filter(id=user_id).exists(): return Response({'error': 'User is already a member'}, status=status.HTTP_400_BAD_REQUEST) project.members.add(user) return Response({'status': 'member added'}) except User.DoesNotExist: return Response({'error': 'User not found'}, status=status.HTTP_404_NOT_FOUND) class TaskViewSet(ModelViewSet): permission_classes = [permissions.IsAuthenticated] filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter] filterset_fields = ['status', 'priority', 'project', 'assigned_to'] search_fields = ['title', 'description'] ordering_fields = ['created_at', 'due_date', 'priority', 'title'] ordering = ['-created_at'] def get_serializer_class(self): if self.action == 'list': return TaskListSerializer elif self.action == 'create': return TaskCreateSerializer return TaskDetailSerializer def get_queryset(self): user = self.request.user queryset = Task.objects.filter( Q(created_by=user) | Q(assigned_to=user) | Q(project__owner=user) | Q(project__members=user) ).distinct() status_filter = self.request.query_params.get('status') priority_filter = self.request.query_params.get('priority') project_filter = self.request.query_params.get('project') assigned_filter = self.request.query_params.get('assigned_to') search_query = self.request.query_params.get('search') if status_filter: queryset = queryset.filter(status=status_filter) if priority_filter: queryset = queryset.filter(priority=priority_filter) if project_filter: queryset = queryset.filter(project_id=project_filter) if assigned_filter: queryset = queryset.filter(assigned_to_id=assigned_filter) if search_query: queryset = queryset.filter( Q(title__icontains=search_query) | Q(description__icontains=search_query) ) return queryset def perform_create(self, serializer): serializer.save(created_by=self.request.user) @action(detail=True, methods=['post']) def add_comment(self, request, pk=None): task = self.get_object() serializer = CommentSerializer(data=request.data) if serializer.is_valid(): serializer.save(task=task, author=request.user) return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) class CommentViewSet(ModelViewSet): serializer_class = CommentSerializer permission_classes = [permissions.IsAuthenticated] def get_queryset(self): return Comment.objects.filter(author=self.request.user) def perform_create(self, serializer): serializer.save(author=self.request.user) @api_view(['GET']) def dashboard_stats(request): if not request.user.is_authenticated: return Response({'error': 'Authentication required'}, status=status.HTTP_401_UNAUTHORIZED) user = request.user stats = { 'total_tasks': Task.objects.filter( Q(created_by=user) | Q(assigned_to=user) ).count(), 'tasks_todo': Task.objects.filter( Q(created_by=user) | Q(assigned_to=user), status='todo' ).count(), 'tasks_in_progress': Task.objects.filter( Q(created_by=user) | Q(assigned_to=user), status='in_progress' ).count(), 'tasks_review': Task.objects.filter( Q(created_by=user) | Q(assigned_to=user), status='review' ).count(), 'tasks_done': Task.objects.filter( Q(created_by=user) | Q(assigned_to=user), status='done' ).count(), 'total_projects': Project.objects.filter( Q(owner=user) | Q(members=user) ).distinct().count(), } return Response(stats) @api_view(['GET']) def get_current_user(request): if request.user.is_authenticated: serializer = UserSerializer(request.user) return Response(serializer.data) return Response({'detail': 'Not authenticated'}, status=status.HTTP_401_UNAUTHORIZED) @api_view(['GET']) @permission_classes([permissions.AllowAny]) def health_check(request): """ Health check endpoint for load balancers and monitoring """ try: from django.db import connection with connection.cursor() as cursor: cursor.execute("SELECT 1") db_status = "healthy" except Exception as e: db_status = f"unhealthy: {str(e)}" try: from django.core.cache import cache cache.set('health_check', 'test', 1) cache_status = "healthy" if cache.get('health_check') == 'test' else "unhealthy" except Exception as e: cache_status = f"unhealthy: {str(e)}" return Response({ 'status': 'healthy', 'timestamp': timezone.now().isoformat(), 'database': db_status, 'cache': cache_status, 'version': '1.0.0' }) @api_view(['GET']) @permission_classes([permissions.AllowAny]) def api_root(request, format=None): """ Root endpoint for TaskFlow API """ return Response({ 'message': 'Welcome to TaskFlow API', 'version': '1.0.0', 'endpoints': { 'health': reverse('health_check', request=request, format=format), 'register': reverse('register', request=request, format=format), 'login': reverse('token_obtain_pair', request=request, format=format), 'refresh_token': reverse('token_refresh', request=request, format=format), 'current_user': reverse('current_user', request=request, format=format), 'user_profile': reverse('user_profile', request=request, format=format), 'users': reverse('user_list', request=request, format=format), 'projects': reverse('project-list', request=request, format=format), 'tasks': reverse('task-list', request=request, format=format), 'comments': reverse('comment-list', request=request, format=format), 'dashboard_stats': reverse('dashboard_stats', request=request, format=format), }, 'authentication_required': { 'public': ['health', 'register', 'login', 'refresh_token'], 'protected': ['current_user', 'user_profile', 'users', 'projects', 'tasks', 'comments', 'dashboard_stats'] } }) @api_view(['POST']) @permission_classes([permissions.IsAuthenticated]) def team_invite(request): """ Invite user to team """ email = request.data.get('email') project_id = request.data.get('project_id') if not email or not project_id: return Response( {'error': 'Email and project_id are required'}, status=status.HTTP_400_BAD_REQUEST ) try: project = Project.objects.get(id=project_id, owner=request.user) user_to_invite = User.objects.get(email=email) project.members.add(user_to_invite) return Response({ 'status': 'success', 'message': f'User {user_to_invite.email} invited to project' }) except Project.DoesNotExist: return Response( {'error': 'Project not found or access denied'}, status=status.HTTP_404_NOT_FOUND ) except User.DoesNotExist: return Response( {'error': 'User with this email not found'}, status=status.HTTP_404_NOT_FOUND ) class InvitationViewSet(ModelViewSet): permission_classes = [permissions.IsAuthenticated] def get_serializer_class(self): if self.action == 'create': return InvitationCreateSerializer return InvitationSerializer def get_queryset(self): return Invitation.objects.filter( Q(invited_by=self.request.user) | Q(email=self.request.user.email) ).distinct() def perform_create(self, serializer): invitation = serializer.save(invited_by=self.request.user) # Отправляем email (в продакшене раскомментировать) # send_invitation_email(invitation) @action(detail=True, methods=['post']) def resend(self, request, pk=None): """Повторная отправка приглашения""" invitation = self.get_object() if invitation.invited_by != request.user: return Response({'error': 'Недостаточно прав'}, status=status.HTTP_403_FORBIDDEN) if invitation.status != 'pending': return Response({'error': 'Можно повторно отправить только pending приглашения'}, status=status.HTTP_400_BAD_REQUEST) return Response({'status': 'Приглашение отправлено повторно'}) @action(detail=True, methods=['post']) def respond(self, request, pk=None): """Ответ на приглашение (принять/отклонить)""" invitation = self.get_object() if invitation.email != request.user.email: return Response({'error': 'Это приглашение не для вас'}, status=status.HTTP_403_FORBIDDEN) serializer = InvitationResponseSerializer(data=request.data) if serializer.is_valid(): accept = serializer.validated_data['accept'] if invitation.status != 'pending': return Response({'error': 'Приглашение уже обработано'}, status=status.HTTP_400_BAD_REQUEST) if invitation.is_expired(): invitation.status = 'expired' invitation.save() return Response({'error': 'Приглашение истекло'}, status=status.HTTP_400_BAD_REQUEST) if accept: invitation.status = 'accepted' invitation.invited_user = request.user invitation.project.members.add(request.user) invitation.save() return Response({'status': 'Приглашение принято'}) else: invitation.status = 'declined' invitation.save() return Response({'status': 'Приглашение отклонено'}) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @api_view(['POST']) @permission_classes([permissions.AllowAny]) def accept_invitation_by_token(request): """Принятие приглашения по токену (публичный endpoint)""" serializer = InvitationAcceptSerializer(data=request.data) if serializer.is_valid(): token = serializer.validated_data['token'] try: invitation = Invitation.objects.get(token=token, status='pending') except Invitation.DoesNotExist: return Response({'error': 'Приглашение не найдено или уже обработано'}, status=status.HTTP_404_NOT_FOUND) if invitation.is_expired(): invitation.status = 'expired' invitation.save() return Response({'error': 'Приглашение истекло'}, status=status.HTTP_400_BAD_REQUEST) if request.user.is_authenticated: if request.user.email != invitation.email: return Response({'error': 'Это приглашение не для вашего email'}, status=status.HTTP_403_FORBIDDEN) invitation.status = 'accepted' invitation.invited_user = request.user invitation.project.members.add(request.user) invitation.save() return Response({'status': 'Приглашение принято'}) else: return Response({'message': 'Для принятия приглашения необходимо войти в систему'}) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @api_view(['GET']) @permission_classes([permissions.IsAuthenticated]) def invitations_list(request): """ Temporary endpoint for invitations """ return Response({ 'message': 'Invitations endpoint is under development', 'invitations': [] })