Search code examples
pythondjangographene-pythongraphene-django

How to limit field access on a model based on user type on Graphene/Django?


Let's say I have a model:

class Employee(models.Model):
    first_name = models.CharField(max_length=40)
    last_name = models.CharField(max_length=60)
    salary = models.DecimalField(decimal_places=2)

I want anyone to be able to access first_name and last_name but only want certain users to be able to read salary because this is confidential data.

And then I want to restrict write/update for salary to an even different kind of user.

How do I restrict field read/write/update depending on the request user?

EDIT:

This is in the GraphQL API context. I am using Graphene. I'd like to see a scalable solution in the resolver function.


Solution

  • QUERIES

    Assuming that you have

    1. a query defined like
        employees = graphene.List(EmployeeType)
    
    1. a resolver for the query like
        def resolve_employees(self, info, **kwargs):
            return Employee.objects.all()
    

    and

    1. permissions on your Employee model called can_view_salary and can_edit_salary

    Then you'll need to define the EmployeeType with a value of salary that is dependent on the user. Something like

    from graphene_django.types import DjangoObjectType
    from myapp.models import Employee
    
    class EmployeeType(DjangoObjectType):
        class Meta:
            model = Employee
            
        def resolve_salary(self, info):
            if info.context.user.has_perm('myapp.can_view_salary'):
                return self.salary
            return None
    

    The important takeaway is that you're creating a custom resolve function for the salary that is switching based on the value of a permission. You don't need to create any other resolvers for first_name and last_name.




    MUTATIONS

    Read the documentation first. But there isn't an example for doing an update.

    In brief, here's the approach that you can take:

    1. Create a method to set the employee in your Mutation method
    class MyMutations(graphene.ObjectType):
         set_employee = SetEmployee.Field()
    
    1. Create a method for SetEmployee that gets the Employee object and updates it. The salary field is ignored for certain users.
    class SetEmployee(graphene.Mutation):
        
        class Arguments:
            id = graphene.ID()
            first_name = graphene.String()
            last_name = graphene.String()
            salary = graphene.String()
        
        employee = graphene.Field(lambda: EmployeeType)
        
        
        @classmethod
        def mutate(cls, root, info, **args):
            employee_id = args.get('employee_id')
            
            # Fetch the employee object by id
            employee = Employee.objects.get(id=employee_id)
            first_name = args.get('first_name')
            last_name = args.get('last_name')
            salary = args.get('salary')
            
            # Update the employee fields from the mutation inputs
            if first_name:
                employee.first_name = first_name
            if last_name:
                employee.last_name = last_name
            if salary and info.context.user.has_perm('myapp.can_edit_salary'):
                employee.salary = salary
            employee.save()
            return SetEmployee(employee=employee)
    

    Note: when this answer was originally written, there was no Decimal field available in Graphene Django -- I avoided this issue by taking a string as an input.