# Managers

## Per-Object Permission Levels

### Motivation

A frequent requirement for information system database records are often not publicly visible. There are
strict rules who can view what. The django permission system allows to restrict access to a whole model but in a lot of
cases the visibility needs to be determined on object level. Let's say we have this data structure:

````
# models.py
class Company(models.Model):
    name = models.CharField(max_length=100)

class User(models.Model):
    name = models.CharField(max_length=100)
    company = models.ForeignKey(Company)

class Project(models.Model):
    name = models.CharField(max_length=100)
    company = models.ForeignKey(Company)
````

We have a multi-tenant system which helps organising projects for different companies. As an employee of company A I
am only allowed to see all projects belonging to company A. If we would restrict access with the django permissions
to the project model, this would not suit our purpose.

### Custom manager

To tackle this problem, this library provides an abstract class (actually, there are two of them) to help structure
these access levels in a reproducible, understandable and DRY way.

If you derive your managers from ``AbstractUserSpecificManager``, you will expose three methods: `visible_for()`,
`editable_for()` and `deletable_for()`. Each methods needs to be implemented per manager class like this:

````
# managers.py
class ProjectManager(AbstractUserSpecificManager):

    def visible_for(self, user):
        return self.get_queryset().filter(company=user.company)

    def editable_for(self, user):
        return self.get_queryset().filter(company=user.company)

    def deletable_for(self, user):
        return self.get_queryset().filter(company=user.company)
````

Now just register the custom manager within the model:

````
# models.py
class Project(models.Model):
    name = models.CharField(max_length=100)
    company = models.ForeignKey(Company)

    objects = ProjectManager()
````

Now you can easily use the given methods in every view, service or serializers as follows:

````
# views.py
def project_list(request):
    project_list = Project.objects.all().visible_for(request.user)
    return render(request, 'project/project_list.html', {'project_list': project_list})
````

Obviously, if you are not listing your projects but want to validate if the user is allowed to create/edit or
delete the given object, you should use the other two methods.

### Best practice

#### General

As a nice tweak, you can skip the manger and use directly a ``QuerySet`` class and use this as a manager. Therefore this
package provides the class ``AbstractUserSpecificQuerySet``.

At first, use a custom queryset class like this:

````
# managers.py
class ProjectQuerySet(AbstractUserSpecificQuerySet):

    def visible_for(self, user):
        return self.filter(company=user.company)

    def editable_for(self, user):
        return self.filter(company=user.company)

    def deletable_for(self, user):
        return self.filter(company=user.company)
````

Take care that you need to register it a little different in the model:

````
# models.py
class Project(models.Model):
    name = models.CharField(max_length=100)
    company = models.ForeignKey(Company)

    objects = ProjectQuerySet.as_manager()
````

Then you'll save the ``.all()`` and can directly use the exposed methods:

````
project_list = Project.objects.visible_for(request.user)
````


#### GloballyVisibleManager

It is advisable to use this manager for every class. This way, you can ensure that if some permissions are added to
a model, they are put in the right place (the `visible_for()` method) and not somewhere in the code.

Secondly you do not have to decide every time you need the manger if you need to use `visible_for()` or not - just
use it all the time.

If you have the case that a certain model does not require any user-level permissions, you can use the
``GloballyVisibleManager``. This manager just returns all records when calling any of its base methods:

````
    def visible_for(self, user):
        return self.all()
````

Usually you would use this manager for metadata like categories. As pointed out above, you could use the base manager
class BUT if you have to add some user-level permissions later on, you reduce the risk of bad pattern in your code.
