bridgekeeper
bridgekeeper copied to clipboard
"R" raises an exception when following through a one-to-many relation
I have the following model structure (approximately):
USER = get_user_model()
class Company(models.Model):
#fields omitted for brevity
class Staff(models.Model):
company = models.ForeignKey(Company, on_delete=models.CASCADE)
user = models.ForeignKey(USER, on_delete=models.CASCADE)
and a permission perms["core.view_company"] = R(staff__user=lambda user: user)
I'm creating API endpoints using Django REST Framework, integrated with bridgekeeper as per the documentation. DRF view is a bog-standard ModelViewSet
, DRF serializer is a bog-standard HyperlinkedModelSerializer
.
Django version: 3.0.3 bridgekeeper version: 0.9
When accessing the API endpoint for a specific company (e.g. /api/company/1/
) I get the following exception from bridgekeeper:
Traceback (most recent call last):
File "C:\Anaconda3\lib\site-packages\rest_framework\views.py", line 343, in check_object_permissions
if not permission.has_object_permission(request, self, obj):
File "C:\Anaconda3\lib\site-packages\bridgekeeper\rest_framework.py", line 166, in has_object_permission
return self.get_permission(request, view, obj).check(request.user, obj)
File "C:\Anaconda3\lib\site-packages\bridgekeeper\rules.py", line 203, in check
return self.left.check(user, instance) or self.right.check(user, instance)
File "C:\Anaconda3\lib\site-packages\bridgekeeper\rules.py", line 322, in check
field = lhs.__class__._meta.get_field(
Exception Type: AttributeError at /api/company/1/
Exception Value: type object 'RelatedManager' has no attribute '_meta'
Note that this exception is not raised when accessing /api/company/
(the correct list of companies is returned)
It seems that the R
syntax in bridgekeeper can't handle following relationships backwards through a many-to-one relationship (although I think it might work if the many-to-one is at the end of the chain?).
Unless I'm doing something wrong, I think this is inconsistent with the Q
and filter
syntax in Django. For example, Company.objects.all().filter(staff__user=user)
returns the expected set of companies. So I'm logging it as a bug!
In the meantime, I'm achieving the same thing with In(lambda user: set([staff.company for staff in user.staff_set.all()]))
but I'm guessing this is less efficient.
So I realised today that I had missed a line in the documentation here that says:
If the argument refers to a foreign key or many-to-many relationship, another
Rule
object.
That made me realise that I should really have been doing perms["core.view_company"] = R(staff=R(user=lambda user: user))
which worked as I expected.
That in turn made me realise that this conversion could actually be automated inside R.check
! So I've made PR #23 which does that conversion and added some tests to cover the case. I hope it's a useful contribution!