Django REST Framework - pass model to ViewSet via

2019-07-20 14:03发布

问题:

I'm trying to create a REST-ModelViewSet that has no model predefined, but takes a model when registered with the router. I need this to dynamically add models to my REST-API, without configuring any new viewsets or serializers.

My idea was to pass the model in the kwargs of __init__, but I can't figure out how to correctly do this. Here is what I tried:

//Viewset
class ThemeViewSet(viewsets.ModelViewSet):
    def __init__(self, **kwargs):
        self.model = kwargs['model']
        self.serializer_class = None
        super(ThemeViewSet, self).__init__(**kwargs)

    def get_serializer_class(self):
        if self.serializer_class is not None:
            return self.serializer_class

        class ThemeSerializer(serializers.HyperlinkedModelSerializer):
            class Meta:
                model = self.model

        self.serializer_class = ThemeSerializer
        return self.serializer_class

//Router:
router = routers.DefaultRouter()
router.register(r'mytheme', ThemeViewSet(model=mytheme), base_name='mytheme')

Now, if I try to print self.model in __init__, it correctly shows <class 'myapp.models.mytheme'> in the console, but Django still returns an error:

AttributeError at /api/mytheme/
This method is available only on the view class.

This error is raised by the classonlymethod-decorator. I don't really know what to make of this, is there any way to pass the model to __init__, or is there a different approach that I can try?

(I know that wq.db.rest has a router that does what I want, but I don't want to use wq. I haven't tried tastypie, would that make it easier/possible?)

Thanks in advance!

回答1:

Django REST Framework expects that a ViewSet class is passed into the router, not a view instance. This is because the instance has to be created for each request, which prevents a lot of ugly issues with shared state and also follows the standard Django class-based views.

You may have better luck with having a method that creates a customized ViewSet class based on the model that is passed into it:

class ThemeViewSet(viewsets.ModelViewSet):

    @classmethod
    def create_custom(cls, **kwargs):
        class CustomViewSet(cls):
            model = kwargs["model"]
            queryset = kwargs["model"].objects.all()

        return CustomViewSet

Note that I'm also setting the queryset for the view, and DRF no longer accepts just a model since 2.4 was released.

This will create a new class each time it is called, and the model will automatically be set to the model that is passed into it. When registering it with the router, you would do something like:

router.register(r'mytheme', ThemeViewSet.create_custom(model=mytheme), base_name='mytheme')

This way you will still be passing a ViewSet class to the router, but it will be customized for the model that is passed in. You must make sure to set the base_name, or the router won't be able to generate the view names and you will eventually run into errors.