How do I best restrict by user and by data model using Django?

112 views Asked by At

I'm using django-guardian and I encountered some issues with the default mixins. And I want to know if there's a better way to do this. GitHub Link: https://github.com/iaggocapitanio1/django_homepage

Problem:

If I want to limit access at both the model and object levels, using these two mixins (PermissionRequiredMixin, PermissionListMixin) is not a very easy task. Because the permissions_required attribute is overridden. To get around this I had to create a new attribute "object_permission" and do the following:

Model Looks like:

# Create your models here.
from django.db import models
from localflavor.br import models as localModels
from django.contrib.auth.models import AbstractUser

class User(AbstractUser):
    pass


class Customer(models.Model):
    user: User = models.OneToOneField(User, on_delete=models.CASCADE)

    def __str__(self):
        return f'{self.user.first_name}  {self.user.last_name}'


class Company(models.Model):
    user: User = models.OneToOneField(User, on_delete=models.CASCADE)
    customer = models.ForeignKey(Customer, on_delete=models.CASCADE, related_name='comapnies')

    def __str__(self):
        return f'{self.user.first_name}  {self.user.last_name}'


class Project(models.Model):
    name = models.CharField(max_length=100)
    owner = models.ForeignKey(Customer, on_delete=models.CASCADE, related_name='projects')

    class Meta:
        permissions = (('read_project', 'Read Project'),)

    def __str__(self):
        return self.name


class House(models.Model):
    rooms = models.IntegerField()
    postal_code = localModels.BRPostalCodeField()
    project = models.ForeignKey(Project, on_delete=models.CASCADE)


Here I needed to create a new attribute ("object_permission") to limit object-level access in the View:

class ProjectsListView(PermissionRequiredMixin, PermissionListMixin, ListView):
    template_name = 'home/projects.html'
    model = models.Project
    permission_required = ["homepage.view_project"]
    object_permission = ["read_project"]
    redirect_field_name = 'next'
    login_url = 'login/'

    get_objects_for_user_extra_kwargs = {}

    def get_object_permission(self, request: HttpRequest = None) -> List[str]:
        if isinstance(self.object_permission, str):
            perms = [self.object_permission]
        elif isinstance(self.object_permission, Iterable):
            perms = [p for p in self.object_permission]
        else:
            raise ImproperlyConfigured("'PermissionRequiredMixin' requires "
                                       "'permission_required' attribute to be set to "
                                       "'<app_label>.<permission codename>' but is set to '%s' instead"
                                       % self.permission_required)
        return perms

    def get_get_objects_for_user_kwargs(self, queryset):
        return dict(user=self.request.user,
                    perms=self.get_object_permission(self.request),
                    klass=queryset,
                    **self.get_objects_for_user_extra_kwargs)


@receiver(post_save, sender=models.Project)
def project_post_save(sender, **kwargs):
    """
    Create a Profile instance for all newly created User instances. We only
    run on user creation to avoid having to check for existence on each call
    to User.save.
    """
    project: models.Project = kwargs["instance"]
    created: bool = kwargs["created"]
    if created:
        user = models.User.objects.get(pk=project.owner.user.id)
        assign_perm("read_project", user, project)

Am I using the right approach to filter data relative to each user? How do I combine both the page access limitation and the relative data of each user in a class model view?

0

There are 0 answers