antijob / neuro-parser

Other
3 stars 0 forks source link

Rest API #187

Open SahDoum opened 4 months ago

SahDoum commented 4 months ago

Создание REST API для Django проекта

Описание

REST API с аутентификации по токену. Токены должны быть двух типов — пользовательские и администраторские. Пользовательские токены позволяют только просматривать модели, но не изменять их. Методы, вызывающие Fetcher и IncidentPredictor, должны быть ограничены по количеству запросов в секунду (RPS). Администраторские токены не имеют ограничений на изменения и имеют свои ограничения по RPS.

Модели

  1. IncidentType: Разрешить только изменения полей description, chat_gpt_prompt и is_active. Запретить создание и удаление.
  2. MediaIncident, Source и Article: Реализовать полные CRUD-операции.

API Endpoints

Задачи

  1. Настройка Django REST Framework и DRF Token Auth

    • Добавить djangorestframework и rest_framework.authtoken в INSTALLED_APPS.
    • Настроить аутентификацию по токену в settings.py.
    • Создать новое Django приложение под названием api.
  2. Создание сериализаторов

    • Создать сериализаторы для моделей IncidentType, MediaIncident, Source и Article в api/serializers.py.
  3. Создание ViewSet-ов с ограничениями по ролям

    • Создать ViewSet-ы для моделей IncidentType, MediaIncident, Source и Article в api/views.py.
    • Добавить пользовательские пермишены для ограничения доступа на основе токенов.
  4. Создание URL-ов

    • Определить маршруты для API в api/urls.py.
  5. Реализация Fetch и Predict эндпоинта с ограничением по RPS

    • Создать пользовательский эндпоинт в api/views.py для приема URL статей, создания объектов Article и вызова Fetcher.fetch_article(article) и IncidentPredictor.predict(article) с ограничением по RPS.
  6. Настройка прав доступа и ограничений по RPS

    • Установить соответствующие права доступа для каждого эндпоинта в api/views.py.
    • Настроить middleware для ограничения RPS.
  7. Обновление главного URL-файла

    • Включить URL-ы API в главный urls.py проекта.
  8. Тестирование

    • Написать тесты для всех эндпоинтов в api/tests.py.

Подробные шаги

  1. Настройка Django REST Framework и DRF Token Auth

    # settings.py
    INSTALLED_APPS = [
       ...
       'rest_framework',
       'rest_framework.authtoken',
       'api',  # новое приложение
    ]
    
    REST_FRAMEWORK = {
       'DEFAULT_AUTHENTICATION_CLASSES': [
           'rest_framework.authentication.TokenAuthentication',
       ],
       'DEFAULT_PERMISSION_CLASSES': [
           'rest_framework.permissions.IsAuthenticated',
       ],
    }
    python manage.py migrate
    python manage.py drf_create_token <username>

Создание сериализаторов

# api/serializers.py
from rest_framework import serializers
from server.apps.core.models import IncidentType, MediaIncident, Source, Article

class IncidentTypeSerializer(serializers.ModelSerializer):
    class Meta:
        model = IncidentType
        fields = ['id', 'description', 'chat_gpt_prompt', 'is_active']

class MediaIncidentSerializer(serializers.ModelSerializer):
    class Meta:
        model = MediaIncident
        fields = '__all__'

class SourceSerializer(serializers.ModelSerializer):
    class Meta:
        model = Source
        fields = '__all__'

class ArticleSerializer(serializers.ModelSerializer):
    class Meta:
        model = Article
        fields = '__all__'

Создание ViewSet-ов с ограничениями по ролям

# api/permissions.py
from rest_framework.permissions import BasePermission, SAFE_METHODS

class IsAdminOrReadOnly(BasePermission):
    def has_permission(self, request, view):
        if request.method in SAFE_METHODS:
            return True
        return request.user.is_staff

class IsAdminOrRestrictedPost(BasePermission):
    def has_permission(self, request, view):
        if request.method in SAFE_METHODS:
            return True
        if request.method == 'POST':
            return request.user.is_staff
        return request.user.is_staff
# api/views.py
from rest_framework import viewsets, status
from rest_framework.response import Response
from server.apps.core.models import IncidentType, MediaIncident, Source, Article
from .serializers import IncidentTypeSerializer, MediaIncidentSerializer, SourceSerializer, ArticleSerializer
from .permissions import IsAdminOrReadOnly, IsAdminOrRestrictedPost
from rest_framework.decorators import action
from rest_framework.throttling import UserRateThrottle, AnonRateThrottle

class IncidentTypeViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = IncidentType.objects.all()
    serializer_class = IncidentTypeSerializer
    permission_classes = [IsAdminOrReadOnly]

    def update(self, request, *args, **kwargs):
        partial = kwargs.pop('partial', False)
        instance = self.get_object()
        serializer = self.get_serializer(instance, data=request.data, partial=partial)
        serializer.is_valid(raise_exception=True)
        self.perform_update(serializer)

        if getattr(instance, '_prefetched_objects_cache', None):
            instance._prefetched_objects_cache = {}

        return Response(serializer.data)

    def create(self, request, *args, **kwargs):
        return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED)

    def destroy(self, request, *args, **kwargs):
        return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED)

class MediaIncidentViewSet(viewsets.ModelViewSet):
    queryset = MediaIncident.objects.all()
    serializer_class = MediaIncidentSerializer
    permission_classes = [IsAdminOrReadOnly]

    @action(detail=True, methods=['get'])
    def by_article(self, request, pk=None):
        incidents = self.queryset.filter(article_id=pk)
        serializer = self.get_serializer(incidents, many=True)
        return Response(serializer.data)

class SourceViewSet(viewsets.ModelViewSet):
    queryset = Source.objects.all()
    serializer_class = SourceSerializer
    permission_classes = [IsAdminOrReadOnly]

    @action(detail=True, methods=['get'])
    def articles(self, request, pk=None):
        source = self.get_object()
        articles = Article.objects.filter(source=source)
        serializer = ArticleSerializer(articles, many=True)
        return Response(serializer.data)

    @action(detail=True, methods=['get'])
    def media_incidents(self, request, pk=None):
        source = self.get_object()
        articles = Article.objects.filter(source=source)
        incidents = MediaIncident.objects.filter(article__in=articles)
        serializer = MediaIncidentSerializer(incidents, many=True)
        return Response(serializer.data)

class ArticleViewSet(viewsets.ModelViewSet):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer
    permission_classes = [IsAdminOrRestrictedPost]

    @action(detail=True, methods=['get'])
    def by_media_incident(self, request, pk=None):
        articles = self.queryset.filter(media_incident_id=pk)
        serializer = self.get_serializer(articles, many=True)
        return Response(serializer.data)

    class UserRateThrottle(UserRateThrottle):
        rate = '10/hour'

    class AdminRateThrottle(UserRateThrottle):
        rate = '100/hour'

    @action(detail=False, methods=['post'])
    def fetch_and_predict(self, request):
        url = request.data.get('url')
        if not url:
            return Response({'error': 'URL is required'}, status=400)

        # Создание объекта Article
        article = Article.objects.create(url=url)

        # Вызов внешних методов
        Fetcher.fetch_article(article)
 ### Создание URL-ов
# api/urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import IncidentTypeViewSet, MediaIncidentViewSet, SourceViewSet, ArticleViewSet

router = DefaultRouter()
router.register(r'incident-types', IncidentTypeViewSet)
router.register(r'media-incidents', MediaIncidentViewSet)
router.register(r'sources', SourceViewSet)
router.register(r'articles', ArticleViewSet)

urlpatterns = [
    path('', include(router.urls)),
]

Настройка прав доступа и ограничений по RPS

Обновление главного URL-файла

# server/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include('api.urls')),
]

Тестирование

Как генерируются пользователи по токенам

Для генерации токенов для пользователей используется rest_framework.authtoken. После добавления rest_framework.authtoken в INSTALLED_APPS и выполнения миграций, можно создать токены для существующих пользователей с помощью команды:

python manage.py drf_create_token <username>

Эта команда создаст и выведет токен для указанного пользователя. Пользовательские токены могут быть использованы для аутентификации при запросах к API, добавляя токен в заголовок запроса:

Authorization: Token <user_token>

Токены могут быть различными для администраторов и обычных пользователей, что позволяет реализовать разные уровни доступа и ограничения по RPS.

SahDoum commented 4 months ago

Внеси туда сразу установку какого нибудь свагера, чтобы была страница документации