Наличие одного единственного общего Viewset, который может быть разрешен через его базовое имя маршрутизатора на Django Rest Framework

Мой вопрос больше касается рефакторинга, лучших практик и потенциальных уязвимостей при организации моих маршрутов и представлений в DRF.

Мое приложение очень простое, и все представления должны быть описаны одинаково. Возьмем для примера мой файл views.py:

# views.py
class TrackView(viewsets.ModelViewSet):
    queryset = Track.objects.all()
    serializer_class = TrackSerializer

class AlbumView(viewsets.ModelViewSet):
    queryset = Album.objects.all()
    serializer_class = TrackSerializer

class ArtistView(viewsets.ModelViewSet):
    queryset = Artist.objects.all()
    serializer_class = ArtistSerializer

class ChartView(viewsets.ModelViewSet):
    queryset = Chart.objects.all()
    serializer_class = ChartSerializer

А в urls.py я могу сделать:

#urls.py

router = DefaultRouter()
router.register(r'tracks', TrackView)
router.register(r'albums', AlbumView)
router.register(r'artists', ArtistsView)
router.register(r'charts', ChartsView)

urlpatterns = [
    path('', include(router.urls)),
    path('api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]

В этом сценарии каждый новый вид является копией логики друг друга, только меняя название модели, которую они используют, и для каждой новой добавленной модели, которая нуждается в представлении, я должен буду создавать один и тот же повторяющийся код в urls.py и views.py.

Вместо того, чтобы делать так, я рефакторизовал его следующим образом:

# views.py
from rest_framework import viewsets


class APIView(viewsets.ModelViewSet):
    queryset = None

    def get_queryset(self):
        return eval(self.basename).objects.all()

    def get_serializer_class(self):
        return eval(self.basename+'Serializer')


# urls.py

from django.urls import path, include
from rest_framework.routers import DefaultRouter
from top_charts.views import *
from .routes import ROUTE_NAMES

router = DefaultRouter()

for route in ROUTE_NAMES:
    router.register(r''+route.lower()+'s', APIView, basename=route)

urlpatterns = [
    path('', include(router.urls)),
    path('api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]

# Here in routes.py would be the only place I'd need to update when I have a new model

ROUTE_NAMES = [
    'Track',
    'Album',
    'Artist',
    'Chart'
]

Это работает, и я могу получить доступ к урлам всех конечных точек просто отлично, и это имеет гораздо более чистый код. Мой вопрос: является ли это хорошей практикой или вы видите какие-либо проблемы в моей реализации?

Всегда есть опасения, когда речь идет об использовании eval(), но я не мог прагматично подумать, что здесь может пойти не так, поскольку ROUTE_NAMES определяет оценку, и это полностью под моим контролем, так что это не похоже на место, где я оставляю потенциальную уязвимость.

Кроме того, я не встречал такого рода предложений по реализации нигде в документации DRF. Они всегда идут по пути повторения логики кода представлений, что вызывает у меня подозрения и вопросы, почему.

Часть за частью:

Что касается URL, я не думаю, что то, что вы сделали, очень плохо, но это определенно не лучшая практика. Я вижу три основные проблемы.

  1. Наличие маршрутов в файле URLs позволяет вам видеть представления и пути напрямую. Разделение их на два файла не очень полезно. Вы экономите 2 строки кода, но это не делает код более эффективным и не делает его чище.
  2. Проблема, с которой вы можете столкнуться, заключается в том, что каждый раз, когда вам нужно добавить маршрут, который не следует той же схеме, что и все остальные, вам придется добавлять его отдельно. Или если вам нужно изменить один из уже существующих маршрутов, вам придется удалить его из списка и, опять же, добавить другой маршрут отдельно. У вас больше файлов и данных для обслуживания, но каждый маршрут может быть четко определен в одной строке в urls.py.
  3. Предположим, у вас есть следующее:
ROUTE_NAMES = [
    'Track',
    'Album',
    'Artist'
    'Chart'
]

Выглядит так же, верно? Но после третьего элемента не хватает запятой, и теперь у вас есть приложение, которое работает совершенно по-другому, не падая при запуске сервера, потому что код на самом деле в полном порядке. Такое расположение маршрутов является источником ошибок, которых можно легко избежать, просто поставив одну router.register на строку.

По поводу взглядов у меня также есть 2 основных вопроса.

  1. Первое - это, по сути, пункты 1 и 2, которые я объяснял по поводу объединенных URL: вы экономите строки кода, но это не делает код более эффективным, и в тот момент, когда вам нужно добавить больше функциональности к одному представлению, вам придется переделывать его, потому что нет возможности изменить только одно, оставив остальные такими, какие они есть.
  2. Большое беспокойство вызывает использование eval, как вы уже догадались. Да, я тоже не вижу способа использовать это, но не лучше ли просто не использовать его? Если вам действительно нужно сохранить эти несколько строк кода, есть другие способы сделать то, что вы задумали, не используя eval или другие опасные методы. Например, используя getattr:
from rest_framework import viewsets
from . import models, serializers


class APIView(viewsets.ModelViewSet):
    queryset = None

    def get_queryset(self):
        return getattr(models, self.basename).objects.all()

    def get_serializer_class(self):
        return getattr(serializers, self.basename + 'Serializer')

Таким образом, достигается то же самое без использования слишком опасного метода, ограничивая элементы, которые вы можете получить, тем, что было объявлено или импортировано в models.py и serializers.py, вместо того, чтобы разрешить выполнение любого выражения. Однако это все равно не рекомендуется, потому что проблема, о которой я говорил в пункте 1, остается.

В начале вашего вопроса вы говорите о рефакторинге. Я бы сказал, что Django и DRF не рассматривают рефакторинг в своих руководствах и документации, потому что они предоставляют очень простые примеры и случаи использования своего программного обеспечения. Ваш пример приложения тоже очень маленький, и код очень чистый, так что нет никаких реальных причин для рефакторинга. Я мог бы часами говорить о том, когда и как рефакторить код, но, как правило, не рефакторите то, что хорошо понятно, если эффективность не критична.

Вернуться на верх