0

I have 3 objects - Company, Expert and RecentProject where RecentProject has ForeignKey to Company and ManyToMany relation to expert:

class RecentProject(BaseProfileObject):
    company = models.ForeignKey(
        'experts.Company',
        verbose_name=_("Company"),
        blank=True,
        null=True,
        related_name='projects')
    experts = models.ManyToManyField(
        "Expert",
        verbose_name=_("Experts"),
        blank=True,
        null=True,
        related_name='projects')
    class Meta:
        verbose_name = _("Recent project")
        verbose_name_plural = _("Recent projects")

What i want is to be able to post new recent project from endpoints like

api/v1/companies/1/recentprojects/ and api/v1/experts/1/recentprojects/

In 1st case the seralizer should be able to validate (or rather make permission check) if user can make changes to company and in 2nd case if user can make changes to expert. What i can't seem to find is an example of how to make permissions be aware of parent object (company in 1st case and expert in 2nd).

I understand that it can be done with permissions so i created permission like:

class UserCanModifyCompany(permissions.BasePermission):
    def has_permission(self, request, view):
        # Do check here
        return False

And i added it to my viewset: class RecentProjectViewSet(viewsets.ModelViewSet): queryset = RecentProject.objects.all() serializer_class = RecentProjectSerializer

@permission_classes((UserIsAuthenticatedExpert, UserCanModifyCompany))
def create(self, request, *args, **kwargs):
    return super(RecentProjectViewSet, self).create(request, *args, **kwargs)

@permission_classes((UserIsAuthenticatedExpert, UserCanModifyCompany))
def update(self, request, *args, **kwargs):
    return super(RecentProjectViewSet, self).update(request, *args, **kwargs)

But how can i make sure the permission check has info available for company/expert access check. Or do i need to create different viewsets for both company/expert recent projects saving and have different permission classes for both of them?

1 Answer 1

2

First of all, creating two different endpoints for the same resource is not a good idea. I suggest you use single endpoint: api/v1/recentprojects/. Having said that, I have been in similar situation, and I think your best option is to use @detail_route on Company and Expert viewsets. For example, in your ExpertViewSet:

@detail_route(methods=['post'], , permission_classes=[UserIsAuthenticatedExpert, UserCanModifyCompany])
    def recentprojects(self, request, pk=None):
        expert = self.get_object()
        serializer = RecentProjectSerializer(data=request.data)
        if serializer.is_valid():
            recent_project = serializer.save(company=somecompany)
            recent_project.experts.add(expert)
            return Response({'status': 'ok'})
        else:
            return Response(serializer.errors,
                            status=status.HTTP_400_BAD_REQUEST)

This will let you post new recent projects to api/v1/experts/{pk}/recentprojects/. As for permissions, you need to implement has_object_permission since this is a single object endpoint:

class UserIsAuthenticatedExpert(permissions.BasePermission):

    def has_object_permission(self, request, view, obj):
        return some_condition

You can also override get_queryset together with self.action to filter Expert or Company instances that the endpoint can operate on, but that will raise 404 errors instead of 403.

Sign up to request clarification or add additional context in comments.

7 Comments

Thanks for an answer. I actualy tried using UserCanModifyCompany with has_object_permission method but that method was never called and all projects that were posted via api were saved and none got 403.
Also, What determines the url of the detail route? Your detail_route decorator has request method and permission classes. Where does the url come from - and when i use this detail route with ExpertViewSet then what url takes care of this posting?
@OdifYltsaeb by default, the function name will be used as the part of the url after the resource URL, i.e. ^experts/pk/function_name. This is determined by the router. To override it, pass url_path='recent-projects' to detail_route decorator. See Routers docs for details: django-rest-framework.org/api-guide/routers/…
awesome. Thanks! can you also point me towards page that can tell me how to create detail route for patch/update too at ../recentprojects/<pk>/ ? Just setting another method for this detail route that you wrote in your example, does not work.
It looks like i got past that - i created another detail view for patch and delete and i set url_path='recentprojects/{recentproject_pk} for that detail view. I also set RecentProjectSerializer lookup_url_kwarg to "recentproject_pk". But something is not working because i get keyerror at router get_urls() because "recentproject_pk" is not in context...
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.