Object level permissions with relations

Imagine I have an app with following models:

Project

  • owner
  • field_proj_a
  • field_proj_b

Dataset

  • usecase (ForeignKey)
  • derived_owner (a calculated property, reflecting the owner field of the Project, readonly)
  • field_data_a
  • field_data_b

and the regular django “User”.

Of course the example above is minimal. I got a few more models, with a range of different relations to other fields, where the hierarchy is derived from the Project “downwards”. Hope that makes sense.

I’ve read through the usual sources in the net. What I thought of so far was:

  • Creating the owner from the logged-in user on Project creation (in perform_create of Project view)
  • Filtering Querysets to owner of respective model (therefore adding the calculated property “derived_owner”, based on Project ownership)
  • Setting an “IsOwner” permission as standard additionally to “IsAuthenticated”, checking the “derived_owner” or “owner” of the respective model.

Now when a user creates a Dataset, the user chooses the project it is referring to. How do I make sure the objects referred to via ForeignKey (and other relational field types) are checked for having the logged-in user as the owner (of the referred Project) in a POST/WRITE (to Dataset) request?
With the setup described above, a non-friendly user could still “attach” his new Dataset to a project he does not have access to (e.g. by guessing the FK). How do I mitigate this?

The easiest way to do that is to either leverage the clean function within your forms, or set the queryset attribute for the ChoiceField being used.

(And yes, a comprehensive object-level permissions system can get incredibly intricate.)

Thanks for the prompt reply, Ken.

I forgot to mention I am using DRF. Does your proposed solution work with DRF as well?

Absolutely.

Physically, a “Form”, is a Python object in the server. One of its functions is its ability to create an HTML form - but that HTML is not the “Form”.

Another function is to bind data to the form fields. Yes, in the typical / common case, that data comes from an HTML form post submission. (e.g. my_form = MyForm(request.POST))

If you look at request.POST, you’ll see it’s represented internally as a “dict-like” object.

So what this means is that you can bind any dict to a form. See the examples at The Forms API | Django documentation | Django

Thanks for the continued support, Ken.
I have been looking at the docs a bit deeper.
While the approach via Form appears to be viable, I found the Django Rest Framework Validator features quite helpful for my use case, so I use that in combination with a custom FilterBackend and a default permission.

Thanks again, since your very prompt reply led me in the right direction.