简体   繁体   中英

DRF - Create a Viewset Mixin/Baseclass to inherit a viewset-action

I have a website that is essentially a wiki for a DnD campaign that I am participating in. As such it has articles of Creatures, Characters, Locations and more. I wanted to use Viewsets to access them easily and wanted to use a Viewset action (together with a custom router) to be able to look for individual records not through pk, but through various query-parameters.

I already have something that works for this, now I would like to apply some inheritance to it to not repeat myself. What I'd like to do is something like this:

class WikiBaseViewset (viewsets.ModelViewSet):
    detail_with_params_url_pattern_suffix: str

    @action(detail=True, url_name="detail-params", url_path=detail_with_params_url_pattern_suffix)
    def detail_through_params(self, request, **kwargs):
        if self.detail_with_params_url_pattern_suffix == "":
            raise InvalidViewsetURLException("URL of view 'detail_through_params' of WikiBaseViewset is not defined!")

        model = self.serializer_class.Meta.model
        instance = get_object_or_404(model, **kwargs)
        serializer = self.get_serializer(instance)
        return Response(serializer.data)


class CharacterSerializer (serializers.HyperlinkedModelSerializer):
    class Meta:
        model = wiki_models.Character
        fields = '__all__'


class CharacterViewSet(WikiBaseViewset):
    """Called with URLs: character, character/<str: name>"""
    serializer_class = CharacterSerializer
    queryset = wiki_models.Character.objects.all()
    detail_with_params_url_pattern_suffix = "(?P<name__iexact>.+)"

However, I'm struggling over the fact that the decorator absolutely requires the URL parameter in the base class. Otherwise the code just doesn't compile due to a NameError complaining that detail_with_params_url_pattern_suffix is not defined. If you were to set detail_with_params_url_pattern_suffix="" in the base-class in order to not get an Error when your code is compiled, that still wouldn't matter, as the decorator from my experiments so far still grabs the value of that variable from WikiBaseViewset not CharacterViewSet .

How can I rewrite my BaseClass so that this works? Is there even a way?

I did not find a fully satisfying answer to this problem, but in the end acquiesced with this solution, as it was better than copy pasting.

You might not be able to inherit viewset actions, but you sure can inherit individual methods and then just overwrite them in the child and throw a decorator on top. This lead to this structure:

class WikiBaseViewset (viewsets.ModelViewSet):
    detail_with_params_url_pattern_suffix: str

    def detail_through_params(self, request, **kwargs):
        model = self.serializer_class.Meta.model
        instance = get_object_or_404(model, **kwargs)
        serializer = self.get_serializer(instance)
        return Response(serializer.data)


class CharacterSerializer (serializers.HyperlinkedModelSerializer):
    class Meta:
        model = wiki_models.Character
        fields = '__all__'


class CharacterViewSet(WikiBaseViewset):
    """Called with URLs: character, character/<str: name>"""
    serializer_class = CharacterSerializer
    queryset = wiki_models.Character.objects.all()

    @action(detail=True, url_name="detail-params", url_path="(?P<name__iexact>.+)")
    def detail_through_params(self, request, **kwargs):
        return super().detail_through_params(request, **kwargs)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM