FJNR-inc / dry-rest-permissions

Rules based permissions for the Django Rest Framework
ISC License
75 stars 11 forks source link

AttributeError: 'NoneType' object has no attribute 'Meta' #10

Closed noobmaster19 closed 3 years ago

noobmaster19 commented 3 years ago
Internal Server Error: /api/sales-project/2/getexternalcustomers/
Traceback (most recent call last):
  File "C:\Users\dream\Desktop\crmReact\backend\react_env\lib\site-packages\django\core\handlers\exception.py", line 34, in inner
    response = get_response(request)
  File "C:\Users\dream\Desktop\crmReact\backend\react_env\lib\site-packages\django\core\handlers\base.py", line 115, in _get_response
    response = self.process_exception_by_middleware(e, request)
  File "C:\Users\dream\Desktop\crmReact\backend\react_env\lib\site-packages\django\core\handlers\base.py", line 113, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "C:\Users\dream\Desktop\crmReact\backend\react_env\lib\site-packages\django\views\decorators\csrf.py", line 54, in wrapped_view
    return view_func(*args, **kwargs)
  File "C:\Users\dream\Desktop\crmReact\backend\react_env\lib\site-packages\rest_framework\viewsets.py", line 114, in view
    return self.dispatch(request, *args, **kwargs)
  File "C:\Users\dream\Desktop\crmReact\backend\react_env\lib\site-packages\rest_framework\views.py", line 505, in dispatch
    response = self.handle_exception(exc)
  File "C:\Users\dream\Desktop\crmReact\backend\react_env\lib\site-packages\rest_framework\views.py", line 465, in handle_exception
    self.raise_uncaught_exception(exc)
  File "C:\Users\dream\Desktop\crmReact\backend\react_env\lib\site-packages\rest_framework\views.py", line 476, in raise_uncaught_exception
    raise exc
  File "C:\Users\dream\Desktop\crmReact\backend\react_env\lib\site-packages\rest_framework\views.py", line 493, in dispatch
    self.initial(request, *args, **kwargs)
  File "C:\Users\dream\Desktop\crmReact\backend\react_env\lib\site-packages\rest_framework\views.py", line 411, in initial
    self.check_permissions(request)
  File "C:\Users\dream\Desktop\crmReact\backend\react_env\lib\site-packages\rest_framework\views.py", line 332, in check_permissions
    if not permission.has_permission(request, self):
  File "C:\Users\dream\Desktop\crmReact\backend\react_env\lib\site-packages\dry_rest_permissions\generics.py", line 108, in has_permission
    assert serializer_class.Meta.model is not None, (

Here is the full error trace back.

The following is my code:

class SalesProjectViewSet(viewsets.ModelViewSet):
    queryset = SalesProject.objects.all()
    action_serializers = {
        'retrieve': SalesProjectDetailSerializer,
        'create': SalesProjectSerializer,
        'update': SalesProjectSerializer,
        'partial_update': SalesProjectSerializer,
        'list': SalesProjectListSerializer, 
    }

    permission_classes = (DRYPermissions,)

    @action(detail=True, methods=['GET'], name='getexternalcustomers')
    def getexternalcustomers(self, request,  pk=None):
        queryset = CustomerInformation.objects.exclude(salesproject__in=pk)
        serializer = ProjectCustomerSerializer(queryset, many=True)
        return Response(serializer.data)

    def get_serializer_class(self):
        if hasattr(self, 'action_serializers'):
            print('@@@@@@@here',self,self.action_serializers.get(self.action, self.serializer_class))
            return self.action_serializers.get(self.action, self.serializer_class)
        return super(SalesProjectViewSet, self).get_serializer_class()`

Here is my model:

class SalesProject(models.Model):
...
    @staticmethod
    def has_read_permission(request):
        return True

    def has_object_read_permission(self, request):
        return True

    class Meta:
        permissions = (
            ("can_retrieve_global_projects", "Can Retrieve All Projects"),
            ("can_retrieve_department_projects", "Can Retrieve Department Projects"),
        )

    def __str__(self):
        return self.sales_project_name

The problem arises when i try to access a route that has @action(...) i believe

noobmaster19 commented 3 years ago

It seems that the @list_route has been deprecated , wondering if this package supports @action as well

RignonNoel commented 3 years ago

@neowenshun Can you share the serializer you use with this view at the moment you get the error to allow me reproduce it on my side ?

RignonNoel commented 3 years ago

For me it seems that you just forgot the Meta model in you serializer, but we will see with your code

class Meta:
        model = SalesProject

The line that create your issue is this one, it's a security but it seems that in your console output you don't get the explanation message. We will control that.

https://github.com/FJNR-inc/dry-rest-permissions/blob/a11876ce23a3fed0811d7be6a02b612a90fbdd70/dry_rest_permissions/generics.py#L108-L112

noobmaster19 commented 3 years ago

Thanks , appreciate the quick reply , here is the serializer for the SalesProject

class SalesProjectDetailSerializer(serializers.ModelSerializer):
    items = ProjectItemDetailSerializer(many=True, read_only=True)
    notations = SalesNotationProjectSerializer(many=True, read_only=True)
    ....

    class Meta:
        model = SalesProject
        fields = '__all__'
        depth = 2

The way i structure my project might be different from most people ? Im unsure as there isn't really a generally accepted practice (or so i think) . I have a detail serializer which helps me to serialize all the data for my detail page . It is comprised on multiple serializer methods and serializerfields so that i can customize the depth and fields .

The def getexternalcustomers(self, request, pk=None): is one of the methods of the overall SalesProjectViewSet , which helps me to get data from a related model , CustomerInformation , for a certain form that im using for the detail page.

Here is the serializers that are involved for the custom action def getexternalcustomers(self, request, pk=None):

class ProjectCustomerSerializer(serializers.ModelSerializer):
    class Meta:
        model = CustomerInformation
        fields = '__all__'
        depth = 1
noobmaster19 commented 3 years ago

I realized that the problem only arises when im trying to access a url that was defined by @action[...]

RignonNoel commented 3 years ago

@neowenshun Ok, thanks for the details.

I think that effectivelly the way you get the serializer is the problem, dry-rest-permissions based his logic on the DRF architecture and more particulary on the usage of the get_serializer_class() method inherited by ModelViewSet

https://github.com/FJNR-inc/dry-rest-permissions/blob/a11876ce23a3fed0811d7be6a02b612a90fbdd70/dry_rest_permissions/generics.py#L99-L114

Since your action getexternalcustomers hardcode the usage of a specific serializer class without using the get_serializer_class() the package does not find the serializer class you want to use.


First solution

@action(detail=True, methods=['GET'], name='getexternalcustomers')
def getexternalcustomers(self, request,  pk=None):
    queryset = CustomerInformation.objects.exclude(salesproject__in=pk)
    self.serializer_class = ProjectCustomerSerializer
    serializer = self.get_serializer(queryset, many=True)
    return Response(serializer.data)

With this solution you keep the DRF architecture, you should be compatible with most of the package using the DRF architecture and in bonus you add the context to the serializer that you lacked until now and could be a problem (https://github.com/FJNR-inc/dry-rest-permissions#definition-1)

A serializer with this field MUST have the request accessible via the serializer's context. By default DRF passes the request to all serializers that is creates. However, if you create a serializer yourself you will have to add the request manually like this:

serializer = TestSerializer(data=request.data, context={'request': request})

If this code does not work try to add logic in your get_serializer_class() method directy


Second solution

As described here

If you want to define DRYPermissions for only some subpart of your view you can override the get_permissions function on the view like this:

def get_permissions(self):
    if self.request.method == 'GET' or self.request.method == 'PUT':
        return [DRYPermissions(),]
    return []
noobmaster19 commented 3 years ago

Hms thank you for the very detailed feedback. I have taken your feedback and utilized it within my code and it working . However , i did not use self.serializer_class = ProjectCustomerSerializer as there was an error. It would seem its because i overrode the get_serializer_class method . By specifying the action url there instead i have gotten it to work , thank you so much for your help !

I might have more questions if you wouldnt mind me asking(The correct way to utilize both this package and django's default permissions package). Should i open a new thread for that?

RignonNoel commented 3 years ago

@neowenshun No problem, all the pleasure is for me. It's always interesting to see the usage that other developers have with this package. It allows us to enhance it and learn good new ideas for our own projects!

Feel free to create new tickets for your others questions in order to keep it clear for future developers that search answers. I will make sure to keep all of your questions in considerations to enhance the documentation.