'formfield_for_foreignkey()' with Django Permissions: Fine-Grained Control in Admin

Leader posted 3 min read

formfield_for_foreignkey()' with Django## Mastering formfield_for_foreignkey with Django Permissions: Fine-Grained Control in Admin

Django’s admin is famous for giving developers a fully functional interface out of the box. But real-world apps often need fine-grained access control — different users should see different subsets of related objects.

You may already know about formfield_for_foreignkey, a hook that lets you filter ForeignKey dropdowns in Django Admin. But here’s where it gets really powerful: combine it with Django’s built-in permissions and roles to create a role-aware and secure admin.

In this article, we’ll explore:

✅ Why combine formfield_for_foreignkey with permissions
✅ How to restrict dropdowns using has_perm()
✅ Using groups and roles for finer control
✅ Handling sensitive data with custom rules
✅ Multi-tenant use cases
✅ Best practices


What is formfield_for_foreignkey?

Django provides this method inside ModelAdmin classes to customize the queryset for a ForeignKey dropdown. By default, it lists all related objects in the database. But with formfield_for_foreignkey, you can filter, sort, or restrict that list.

Signature:

def formfield_for_foreignkey(self, db_field, request, **kwargs):
    # custom logic here
    return super().formfield_for_foreignkey(db_field, request, **kwargs)

Why Combine with Permissions?

While formfield_for_foreignkey controls what objects appear, Django’s permission system controls who can see what. Combining them means:

  • Different roles (staff, manager, superuser) see different dropdown options
  • Sensitive objects can be hidden unless a user has explicit permission
  • Multi-tenant applications prevent cross-tenant data leaks

Example 1: Restrict by Permission

Suppose you have Author and Book models. Normally, all authors would appear in the dropdown. But maybe only staff with the app.view_all_authors permission should see everyone. Others should see only their own.

from django.contrib import admin
from .models import Author, Book

@admin.register(Book)
class BookAdmin(admin.ModelAdmin):
    def formfield_for_foreignkey(self, db_field, request, **kwargs):
        if db_field.name == "author":
            if request.user.has_perm("app.view_all_authors"):
                kwargs["queryset"] = Author.objects.all()
            else:
                kwargs["queryset"] = Author.objects.filter(created_by=request.user)
        return super().formfield_for_foreignkey(db_field, request, **kwargs)

✅ Superusers or staff with the permission see all authors.
✅ Other users see only the authors they created.


Example 2: Role-Based Dropdown Filtering

If you’re using Django groups or a role field on your User model, you can scope choices based on role.

def formfield_for_foreignkey(self, db_field, request, **kwargs):
    if db_field.name == "author":
        if request.user.groups.filter(name="Managers").exists():
            # Managers see all active authors
            kwargs["queryset"] = Author.objects.filter(is_active=True)
        else:
            # Regular staff only see their own active authors
            kwargs["queryset"] = Author.objects.filter(
                created_by=request.user, is_active=True
            )
    return super().formfield_for_foreignkey(db_field, request, **kwargs)

✅ Managers have broader visibility.
✅ Staff stay scoped to their own data.


Example 3: Handling Sensitive Data

Sometimes you want to hide sensitive objects unless the user has explicit permission.

def formfield_for_foreignkey(self, db_field, request, **kwargs):
    if db_field.name == "project":
        qs = Project.objects.filter(is_active=True)

        if not request.user.has_perm("app.can_assign_sensitive_projects"):
            qs = qs.exclude(is_sensitive=True)

        kwargs["queryset"] = qs
    return super().formfield_for_foreignkey(db_field, request, **kwargs)

✅ Sensitive projects don’t even appear in the dropdown unless allowed.


Example 4: Multi-Tenant Use Case

In SaaS apps with multiple tenants, you can combine tenant ownership with permissions.

def formfield_for_foreignkey(self, db_field, request, **kwargs):
    if db_field.name == "customer":
        qs = Customer.objects.filter(tenant=request.user.tenant)

        if not request.user.has_perm("app.view_inactive_customers"):
            qs = qs.filter(is_active=True)

        kwargs["queryset"] = qs
    return super().formfield_for_foreignkey(db_field, request, **kwargs)

✅ Users only see customers from their tenant.
✅ Optional permission controls visibility of inactive customers.


Best Practices

  • Always call super() at the end to retain Django’s defaults.
  • Keep queries efficient — avoid heavy joins inside this method.
  • Test with multiple roles (staff, manager, superuser) to avoid hidden bugs.
  • Stay consistent — if you filter dropdowns here, apply the same logic in get_queryset() for list views.

Final Thoughts

formfield_for_foreignkey gives you object-level filtering power, while Django’s permission system gives you user-level control. Together, they create an admin that’s secure, role-aware, and tenant-safe.

Whether you’re managing sensitive data, building a SaaS app, or just keeping the admin clean for staff, mastering this combination will make your Django admin much more robust.


With these examples, you can now:

  • Use permissions to decide who sees what in dropdowns.
  • Implement role-based logic with groups.
  • Keep your admin secure in multi-tenant and sensitive-data scenarios.
If you read this far, tweet to the author to show them you care. Tweet a Thanks

More Posts

Mastering `formfield_for_foreignkey` in Django Admin: A Complete Guide

atifwattoo - Oct 3

How to Restrict ForeignKey Choices in Django Admin

atifwattoo - Oct 3

Django formfield_for_foreignkey() function

atifwattoo - Oct 3

When to Choose FastAPI Over Django or Flask: A Comprehensive Guide with Practical Examples

Esubalew - Jan 22

Demystifying db_field in Django Admin

atifwattoo - Oct 3
chevron_left