Как динамически добавлять конечные точки в набор представлений DRF?

#python #class #django-rest-framework

Вопрос:

У меня есть пара задач сельдерея, которые выполняются таким же образом. Доступ к ним осуществляется через конечные точки, и я хочу централизовать их создание.

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

 # assets/views.py

from django.db.models import QuerySet

from rest_framework.viewsets import ModelViewSet

from tasks.views import CeleryTaskViewSetMixin

from .filters import AssetFilterSet
from .models import Asset
from .serializers import AssetSerializer
from .tasks import cei_assets_crawler, cei_passive_incomes_crawler


class AssetViewSet(ModelViewSet, CeleryTaskViewSetMixin):
    serializer_class = AssetSerializer
    filterset_class = AssetFilterSet
    celery_tasks_endpoints = (
        ("fetch_cei_assets", cei_assets_crawler),
        ("fetch_cei_passive_incomes", cei_passive_incomes_crawler),
    )

    def get_queryset(self) -> QuerySet:
        if self.request.user.is_authenticated:
            return self.request.user.assets.all()
        return Asset.objects.none()  # drf-spectatular
 

Создание конечных точек будет зависеть от CeleryTaskViewSetMixin :

 from typing import Callable, Type

from celery import Task as CeleryTask
from rest_framework.decorators import action
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.status import HTTP_200_OK
from rest_framework.viewsets import ViewSet


def create_endpoint(task: CeleryTask, endpoint_name: str) -> Callable:
    @action(methods=("GET",), detail=False)
    def task_endpoint(self, request: Request) -> Response:
        task.delay(username=request.user.username)
        return Response(status=HTTP_200_OK)

    task_endpoint.__name__ = endpoint_name
    return task_endpoint


class CeleryTaskViewSetMixin:
    def __new__(cls: Type[ViewSet], *args, **kwargs) -> ViewSet:
        for endpoint_name, task in cls.celery_tasks_endpoints:
            setattr(
                cls,
                endpoint_name,
                create_endpoint(task=task, endpoint_name=endpoint_name),
            )
        return super().__new__(cls)
 

Что меня интригует, так это то, что AssetViewSet.get_extra_actions() возвращает два метода:

 [<function create_endpoint.<locals>.task_endpoint at 0x7fab5e6938b0>, <function create_endpoint.<locals>.task_endpoint at 0x7fab5e6b2160>]
 

And I can actually run the task if I mock a request:

 AssetViewSet().fetch_cei_assets(request=Request(HttpRequest()))  # this executes the task!
 

Но a DefaultRouter не создает никакого URL-адреса для этих действий.

Я попытался перейти __new__ прямо к ViewSet , но это не сработало.

Возможно, мой подход не самый лучший, и у вас есть идея получше, но как я могу динамически создавать такие конечные точки?

Большое спасибо!