Hook has_permission + permission function on docType

Hey all,

Using this link about how to use hook and python code to set permission on file, I am struggling to make my code work.

Use case. I have a DocType called meeting with an associated child DocType called Meeting Attendee. A user could have “read” access to a Meeting instance if she/he is part of the attendee list.

Here is what I have but it doesn’t work as now, no one can have access to any meeting. Any pointer on what I might be doing wrong?

Thanks in advance for help, pointers, support :wink:

On hook.py

has_permission = {
“Meeting”: “ifitwala_ed.school_settings.doctype.meeting.meeting.meeting_has_permission”,
}

On the Meeting DocType:

def meeting_has_permission(doc, user=None, permission_type=None):
	attendees=frappe.get_all("Meeting Attendee", filters = {"parent": doc}, fields = ["attendee"])
	attendee_list = [d.attendee for d in attendees]

	if permission_type == "read" and user in attendee_list:
		return True

	return False

system does not support using child field as where condition in get_permission_query_conditions hook. the following changes will make it possible

frappe.model.db_query.py


def get_permission_query_conditions(self):

        valid_operators = ("=", "!=", ">", "<", ">=", "<=", "like", "not like", "in", "not in", "is", 

                 "between")

        condition_methods = frappe.get_hooks("permission_query_conditions", {}).get(self.doctype, [])

        if condition_methods:

            conditions = []

            for method in condition_methods:

                c = frappe.call(frappe.get_attr(method), self.user)

                if c:

                    conditions.append(c)

                    # in case child talbe field used, add child table to tables which will auto inner join

                    token = re.split(' and | or ',c, flags=re.IGNORECASE)

                    for t in token: 

                        field = re.split('|'.join(valid_operators),t) 

                        if len(field)>1: 

                            f = {field[0].replace('(','').replace(' ','').replace('`',''):''}

                            try:

                                f = get_filter(self.doctype, f)

                                tname = ('`tab' + f.doctype + '`')

                                if not tname in self.tables:

                                    self.append_table(tname)

                            except:

                                pass

            return " and ".join(conditions) if conditions else None

what I have done

  1. hooks.py
    permission_query_conditions = {

     "Meeting": "ibp.integrated_budget_and_planning.doctype.meeting.meeting.get_permission_query_conditions",
    

    }

    has_permission = {

     "Meeting": "ibp.integrated_budget_and_planning.doctype.meeting.meeting.has_permission",
    

    }

  2. meetings.py
    from future import unicode_literals

    import frappe

    from frappe.model.document import Document

    class Meeting(Document):

     pass
    

    def get_permission_query_conditions(user):

     if not user: user = frappe.session.user
    
     return """(`attendee`=%(user)s)""" % {
    
             "user": frappe.db.escape(user),
    
         }
    

    def has_permission(doc, user):

     if user in [d.attendee for d in doc.attendees]:
    
         return True
    
     return False
    
  3. in Meetings doctype, assigned a custom role

  4. in user master assign custom role to user.

5 Likes

Oh woohaaaa. thank you so very much @szufisher for your detailed help.
Let me try all of that and I’ll get back to you.

I am a bit confused with the sql query:

return """(`attendee`=%(user)s)""" % {
         "user": frappe.db.escape(user),
     }

attendee is the field name in meeting attendance child table(field name attendees) of meeting doctype, this is simply where condition which used the child table field, the change made to db_query will implicitly add the inner join between master(meeting) and child table(meeting attendance).

1 Like

Yes, Thanks @szufisher .
The sql query threw me an error.
The has_permission function worked as intended.

The issue is now that user have no permission to create any new meeting. How can one go around that.
The idea is that everyone can create a meeting and invite anyone. But anyone can only see the meetings that they have been invited to (or are being part of).

Best,

please provide screen shot of meeting and meeting attendance doctype definition(field name list).

@szufisher, see below.

And whole app is here:

change from return “”“(attendee=%(user)s)”“” % {
“user”: frappe.db.escape(user),
}

to

return “”“(attendee=%(user)s)”“” % {
“user”: frappe.db.escape(user),
}

the quote sign around field name attendee is necessary.

also make sure you changed the db_query. py as above

Thanks, @szufisher.
I think your two codes are the same, but I got it with the quote sign around attendee.

How do you solve the issue to create new meetings. The has_permission code allow for users to only see meetings they have been invited to, but how can anyone create a meeting. It seems then now, no one can create meetings anymore; although, In the doctype meeting, I did give create permission to a whole bunch of roles.

I am a bit wary to change the code straight in the frappe app. This will create issue later on when updating Frappe. (will need to either manually update with git pull, then migrate, then rebuild, etc.) or stag changes.

Have you considered putting in a pull request for it?

I can see so many cases where we need permission on child’s tables. So many documents in HR would benefit from it. And we would then be able to solve the student’s view issue (students should only be seen by instructors who teach them. At the moment, instructors see all students)

Again, I am very thankful for your time and explanations. Much appreciated.

if user added himself/herself in the attendee list, then he/she can successfully create new meeting.

anyway, I changed the code as following, then user as owner is always has the permission to create/display the meeting.

def get_permission_query_conditions(user):

    if not user: user = frappe.session.user

    return """(attendee=%(user)s or tabMeeting.owner=%(user)s)""" % {

            "user": frappe.db.escape(user),

        }

def has_permission(doc, user):

    if doc.is_new():

        return True

    if doc.owner==user or user in [d.attendee for d in doc.attendees]:

        return True

    return False

I tried to overwrite get_permission_query_conditions in custom app hooks, seems it does not work. so only way is to apply PR to merge into core.
I will try, but per my experience in the past, it is not so easy to be accepted into core.

this is the tested working version which do not need change to the core

3 Likes

@szufisher, trying it all now.
Man you’ve been such a huge help. Thank you so much. I’ll get back to you.

It all works!!!
Thanks so much :wink:

Please mark my answer as solution :grin:, otherwise other user will be confused .

Sorry for that… Done :wink:

@szufisher

Will this solution work for reports created using report builder ?

I am facing a similar issue.

In my use case, I have provided user permission to Sales Person doctype to the users who are the Sales Person.

The sales person is added to the sales_team child table of the Sales Invoice. The users are able to see all the Sales Invoice in the List View (though not able to open) but in the report view they can see the details of all the sales invoices.