Search code examples
pythondjangodjango-modelsdecoratoraccess-control

How to pass PK into a method decorator


Hello guys i am trying to implement some form of access control on my views. My programme is structured as such: 1 project has some users which are tied to it in the form of a foreign key. I only wish to allow those whom are involved in it to view this project. The problem however is that the PK i use to query the database for my template is in my URL , users which do not have access to the project can simply change the url query and gain access to the items they do not have access to.

I came across django's user_passes_test method decorator and thought that it is exactly what i needed to implement this user access control.

Here is some code that i have came up with:

My view:

@method_decorator(user_passes_test(project_check(id)),name ='dispatch')
class ProjectDetailView(CreateView):

##this is actually not relavant##
  model = SalesNotation
  fields = ['sales_notes']
  exclude = ['notation_id']
  template_name = 'rnd/projects.html'
  context_object_name = 'form'
##this is actually not relavant##

  def get_context_data(self, **kwargs):
    id = self.kwargs['id']
    context = super(ProjectDetailView, self).get_context_data(**kwargs)
    context['projects'] = SalesProject.objects.filter(sales_project_id = id)

This is my URL:

path('projects/<int:id>/', ProjectDetailView.as_view(), name = 'rnd-projects'),

This is my project model:

class SalesProject(models.Model):
    sales_project_id = models.AutoField(primary_key=True)
    sales_project_name = models.CharField(max_length=100)
    salesExtra = models.ManyToManyField('SalesExtra', blank=True)

Here is my extended user model which i use to keep other information:

class SalesExtra(models.Model):
    user = models.OneToOneField(User,on_delete=models.CASCADE)
    user_type = models.TextField(max_length=500, choices= role)
    contact = models.IntegerField()
    email = models.TextField(max_length=30,default = 'your email here')

Here is the method decorator that im using:

def project_check(user,id):
    return SalesProject.objects.filter(sales_project_id=id).filter(salesExtra__user=user)

However it seems that i am unable to simply pass in the PK from the url as i recieve this error when running the server:

@method_decorator(user_passes_test(project_check(id) , name='dispatch'))
TypeError: project_check() missing 1 required positional argument: 'id

Any help will be greatly appreciated!


Solution

  • You can't. But you can just use UserPassesTestMixin instead:

    from django.contrib.auth.mixins import UserPassesTestMixin
    
    class ProjectDetailView(UserPassesTestMixin, CreateView):
    
    ##this is actually not relavant##
      model = SalesNotation
      fields = ['sales_notes']
      exclude = ['notation_id']
      template_name = 'rnd/projects.html'
      context_object_name = 'form'
    ##this is actually not relavant##
    
      def get_context_data(self, **kwargs):
        id = self.kwargs['id']
        context = super(ProjectDetailView, self).get_context_data(**kwargs)
        context['projects'] = SalesProject.objects.filter(sales_project_id = id)
    
      def test_func(self):
        return SalesProject.objects.filter(sales_project_id=self.kwargs["id"]).filter(salesExtra__user=self.request.user)
    

    Note test_func here which performs check. self.kwargs["id"] will give you id.