Display and manage objects that don’t have direct foreign key relationships
Nonrelated inlines allow you to display and edit objects in an inline format even when they don’t have a traditional foreign key relationship to the parent model. This is useful for many-to-many relationships, complex queries, or custom associations.
Django’s built-in inlines require a foreign key relationship between the parent and child models. Nonrelated inlines remove this restriction, allowing you to display any queryset of objects as an inline formset.
To create a nonrelated inline, inherit from NonrelatedTabularInline or NonrelatedStackedInline and implement two required methods:
admin.py
from django.contrib import adminfrom unfold.admin import ModelAdminfrom unfold.contrib.inlines.admin import NonrelatedTabularInlinefrom .models import Author, Bookclass AuthorBooksInline(NonrelatedTabularInline): model = Book extra = 0 fields = ['title', 'isbn', 'published_date'] def get_form_queryset(self, obj): """Return the queryset of books for this author""" if obj: return Book.objects.filter(authors__in=[obj]) return Book.objects.none() def save_new_instance(self, parent, instance): """Associate a new book with the author""" instance.save() instance.authors.add(parent)@admin.register(Author)class AuthorAdmin(ModelAdmin): inlines = [AuthorBooksInline]
The two required methods are get_form_queryset() and save_new_instance(). These define how to fetch related objects and how to associate new objects with the parent.
Returns the queryset of objects to display in the inline:
admin.py
def get_form_queryset(self, obj): """ Args: obj: The parent model instance Returns: QuerySet: Objects to display in the inline """ if obj is None: return self.model.objects.none() # Your custom logic here return self.model.objects.filter(your_filter=obj)
Defines how to associate a newly created instance with the parent:
admin.py
def save_new_instance(self, parent, instance): """ Args: parent: The parent model instance instance: The new child model instance to associate """ # Save the instance first if needed instance.save() # Then establish the relationship # Examples: # - Many-to-many: instance.parents.add(parent) # - Custom field: instance.parent_id = parent.id; instance.save() # - Through model: ThroughModel.objects.create(parent=parent, child=instance)
from django.db import modelsclass Project(models.Model): name = models.CharField(max_length=200) members = models.ManyToManyField('User', related_name='projects')class User(models.Model): name = models.CharField(max_length=100) email = models.EmailField()
admin.py
from unfold.contrib.inlines.admin import NonrelatedTabularInlineclass ProjectMembersInline(NonrelatedTabularInline): model = User extra = 1 fields = ['name', 'email'] def get_form_queryset(self, obj): if obj: return obj.members.all() return User.objects.none() def save_new_instance(self, parent, instance): instance.save() parent.members.add(instance)@admin.register(Project)class ProjectAdmin(ModelAdmin): inlines = [ProjectMembersInline]
class Region(models.Model): name = models.CharField(max_length=100) country_code = models.CharField(max_length=2)class Store(models.Model): name = models.CharField(max_length=200) country_code = models.CharField(max_length=2) city = models.CharField(max_length=100)
admin.py
class RegionStoresInline(NonrelatedTabularInline): model = Store extra = 0 fields = ['name', 'city'] can_delete = False # Prevent deletion from this inline def get_form_queryset(self, obj): if obj: # Show all stores in the same country return Store.objects.filter(country_code=obj.country_code) return Store.objects.none() def save_new_instance(self, parent, instance): # Set the country code from the region instance.country_code = parent.country_code instance.save() def has_change_permission(self, request, obj=None): # Make the inline read-only return False@admin.register(Region)class RegionAdmin(ModelAdmin): inlines = [RegionStoresInline]