Row Level Security in Django

Okay so imagine I have these 2 model class

class Vendor(models.Model):
    created_at = models.DateTimeField(auto_now_add = True, blank=True, null=True)
    vendor_name = models.CharField(max_length = 50)
    vendor_acronym = models.CharField(max_length = 3)

class Vendor_Details(models.Model):
    created_at = models.DateTimeField(auto_now_add = True, blank=True, null=True)
    vendor_name = models.ForeignKey(Vendor, on_delete = models.CASCADE)
    vendor_number = models.CharField(max_length = 75, blank = True, null = True)
    office_address = models.CharField(max_length = 75, blank = True, null = True)
    owner_name = models.CharField(max_length = 25, blank = True, null = True)

Pardon if there’s any mistake in the code, this is just for example purposes

Now let’s say there’s user A that’s the Person in Charge of Vendor A. I want that person to only be able to see Vendor_Details of Vendor A and only that. Am I able to do it in Django or is it more appropiate to do such thing on the database level? If I’m able to do such thing with the blessings of Django, can someone point me into the light? I’ve seen a couple of threads / article flying around saying “Postgres Row-Level Security”, but I’m using MySQL :confused:

I already have an idea (no idea to implement it tho lmao), which is to assign vendor_acronym to each user. And whenever I want to View say the details, I’ll just filter the queryset. But there are a couple set backs tho

  1. I have to do it in every single view
  2. If the user just modify the url, they’ll still be able to see other details.

Anyway, thank you for whoever reply to this question! :smile:

So, you could extend the User model:

from django.contrib.auth.models import User

class UserProfile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    vendor = models.ForeignKey(Vendor, on_delete=models.CASCADE)

Now, in your views, you can filter the Vendor_Details based on the logged-in user’s vendor:

from django.contrib.auth.decorators import login_required
from .models import UserProfile, Vendor_Details

@login_required
def vendor_details(request):
    user_profile = UserProfile.objects.get(user=request.user)
    vendor_details = Vendor_Details.objects.filter(vendor_name=user_profile.vendor)
    return render(request, 'vendor_details.html', {'vendor_details': vendor_details})

In this example, the @login_required decorator ensures that only logged-in users can access this view. The vendor_details view gets the user’s profile, then filters the Vendor_Details based on the vendor associated with the user’s profile. The filtered vendor_details are then passed to the template.

This approach ensures that a user can only see the Vendor_Details of their associated vendor, even if they try to modify the URL.

As for the concern of having to do this in every single view, you can create a custom mixin or decorator that does this filtering for you, and then use it in your views.

Here’s an example of how you can create a mixin:

class VendorDetailsMixin:
    def get_queryset(self):
        queryset = super().get_queryset()
        user_profile = UserProfile.objects.get(user=self.request.user)
        return queryset.filter(vendor_name=user_profile.vendor)

You can then use this mixin in your views like this:

class VendorDetailsView(VendorDetailsMixin, ListView):
    model = Vendor_Details
    template_name = 'vendor_details.html'

This way, the filtering logic is encapsulated in the mixin and can be reused in multiple views.

This may also be called “multi tenancy”, the django ecosystem has some packages that deals with that.

They both have their benefits and drawbacks, but both do what you’re expecting.

Django does provide the facilities for row-level security, but not out of the box.

For something like what you’re describing here, you’re likely going to want to implement a custom manager for each of Vendor and Vendor_Details. These managers would contain methods that return a subset of the full set of objects based upon the user making the request. (How that’s done within the manager is up for you to determine - whether you create a many-to-many relationship between User and Vendor directly or through some intermediary model is something you need to determine base upon your specific requirements.)

See Managers | Django documentation | Django for more details on model managers.

Additionally, you might want to add a custom has_perm method that you can use within your views for special / unusual situation where the manager may not be sufficient on its own. See the docs starting at Handling authorization in custom backends for more details on that topic.

1 Like

Hiyaa, thank you very much for all the replies from you lot. I LOVE THIS COMMUNITY SO MUCH LMAOO. And sorry for the very late reply, my schedule has been very chaotic.

In regards of using package, my company’s is very wary about using something built by a 3rd party so they prefer using built-in method. But thank you very much for the answers! @leandrodesouzadev

I end up using managers with the help of @anefta + @KenWhitesell answers and it works wonderfully! Even though I’m using a function-based view so I can’t use mixin and I end up doing it manually. But another question is, doing it manually using function-based view doesn’t affect performance right? If so, I’ll probably change it even though I have plenty of view lmao.

Thank you very much again for all of the answers! You lot rocks!!

1 Like

Compared to what?

Keep in mind that all views are functions.

Python classes are just a way of organizing your code, providing a namespace and a mechanism for inheriting functionality. They are not a performance enhancement. (In the general case, they’re less performant than an FBV because of all the internal methods being called.)

(Also note that you do have the ability to implement this type of functionality as a decorator to minimize the amount of code to be replicated among views.)

1 Like