Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to hide/exclude certain fields of foreign key in graphene_django wrt the requested entity?

I have read about how to exclude (hide) certain fields in django and graphene_django in these Links:

  • graphql-python repository
  • Why does my excluded field still appear in this Django form?
  • etc.

Imagine that we have the following Post model which has foreign key to the User model.

apps/posts/models.py

from django.db import models
from apps.users.models import User

class Post(models.Model):
    author = models.ForeignKey(
        User,
        on_delete=models.CASCADE,
        null=False
    )

    created_at = models.DateTimeField(
        auto_now_add=True,
    )

    title = models.TextField(
        null=False,
        max_length=100,
    )

    content = models.TextField(
        null=False,
    )

    def __str__(self):
        return self.title

apps/users/models.py

from django.db import models
from django.contrib.auth.models import AbstractUser

class User(AbstractUser, models.Model):
    phone_no = models.CharField(
        blank=True,
        null=True,
        default="",
        max_length=10,
        verbose_name="Phone Number",
    )

    avatar = models.ImageField(
        null=True,
        upload_to='static',
    )

    USERNAME_FIELD = "username"
    EMAIL_FIELD = "email"

    def __str__(self):
        return self.username

I tried the following but it doesn't work as expected:

apps/posts/schema.py

import graphene
from graphene import Mutation, InputObjectType, ObjectType
from graphene_django.types import DjangoObjectType

from .models import Post


class PostType(DjangoObjectType):
    class Meta:
        model = Post
        exclude_fields = [
            'created_at', #it worked
            'author.password', #the way I tried to hide the foreign key field
            
        ]

class Query(ObjectType):
    posts = graphene.List(
        PostType
    )

    def resolve_posts(self, info):
        #TODO: pagination
        return Post.objects.all()

Screenshot:

insomnia screenshot

How can I hide certain fields of it (like author's password in the above example) in graphql model Types?

like image 396
Mostafa Ghadimi Avatar asked Aug 19 '20 22:08

Mostafa Ghadimi


2 Answers

From the comments, I have understood that you have a UserType class

You can use the exclude option in the meta as,

class UserType(DjangoObjectType):
    class Meta:
        model = User
        exclude = ('password',)

Update

You can also use a custom resolver to check the requested entity

class UserType(DjangoObjectType):
    password = graphene.String()

    def resolve_password(self, info):
        requested_user = info.context.user
        if requested_user.email in ['[email protected]', '[email protected]']:
            return self.password
        return None

    class Meta:
        model = User
        fields = '__all__'
like image 85
JPG Avatar answered Oct 12 '22 02:10

JPG


This is answer only for checking permissions before resolving query field. (So, not an answer to original question)

Something like this would work.


def permission_check_my_field(func):
    @wraps(func)
    def wrapper(self,info,**kwargs):
        user=info.context.user
        if (......) # permit condition here
           return func(self, info,**kwargs)
        else:
            return None
    return wrapper

class  Query(graphene.ObjectType):
    my_field = graphene.Field(...) # or graphene.List or .....
    
    @permission_check_my_field
    def resolve_my_field(......)
      # do your normal work

Update.

Above code works if the user data is enough to check if the field is accessible (like the other answer). However, if you need to check whether or not user has been granted some permission to access that field, then you need to do like this:


def permission_check_in_query(perm):
    def wrapped_decorator(func):
        @wraps(func)
        def wrapper(self,info,**kwargs):
            user=info.context.user
            if user.has_perm(perm) # check if the user has privilege
                return func(self, info,**kwargs)
            else:
                return None 
                # All fields are not nullable, so  `return None' might throw error. You can pass `what you need to return` in decorator argument and use it here , to avoid this.   
        return wrapper
    return wrapped_decorator

class  Query(graphene.ObjectType):
    my_field = graphene.Field(...) # or graphene.List or .....
    
    @permission_check_in_query('model.access_my_field') # your permission code 
    # learn django permissions if you are not sure what it is
    # doesn't have to be django_permission, can be any argument like 'is_it_a_superuser' that you will use to check user privilege. Modify decorator code accordingly
    def resolve_my_field(......)
      # do your normal work

Doing this, you can reuse for any field and any permission. Just add the decorator @permission_check_in_query(your arguments) above any field that needs to be permission-checked before being resolved.

TLDR: This answer is similar to other answer regarding the type of data that API accepts and returns. It just offers reusability and permission check.

like image 2
Sagar Adhikari Avatar answered Oct 12 '22 00:10

Sagar Adhikari