[Fixed]-How to prevent self (recursive) selection for FK / MTM fields in the Django Admin

2👍

You can use a custom ModelForm in the admin (by setting the “form” attribute of your ModelAdmin subclass). So you do it the same way in the admin as you would anywhere else.

10👍

Carl is correct, here’s a cut and paste code sample that would go in admin.py

I find navigating the Django relationships can be tricky if you don’t have a solid grasp, and a living example can be worth 1000 time more than a “go read this” (not that you don’t need to understand what is happening).

class MyForm(forms.ModelForm):
    class Meta:
        model = MyModel

    def __init__(self, *args, **kwargs):
        super(MyForm, self).__init__(*args, **kwargs)
        self.fields['myManyToManyField'].queryset = MyModel.objects.exclude(
            id__exact=self.instance.id)

1👍

You can also override the get_form method of the ModelAdmin like so:

def get_form(self, request, obj=None, **kwargs):
    """
    Modify the fields in the form that are self-referential by
    removing self instance from queryset
    """
    form = super().get_form(request, obj=None, **kwargs)
    # obj won't exist yet for create page
    if obj:
        # Finds fieldnames of related fields whose model is self
        rmself_fields = [f.name for f in self.model._meta.get_fields() if (
            f.concrete and f.is_relation and f.related_model is self.model)]
        for fieldname in rmself_fields:
            form.base_fields[fieldname]._queryset =
                form.base_fields[fieldname]._queryset.exclude(id=obj.id)
    return form

Note that this is a on-size-fits-all solution that automatically finds self-referencing model fields and removes self from all of them 🙂

0👍

I like the solution of checking at save() time:

    def save(self, *args, **kwargs):
        # call full_clean() that in turn will call clean()
        self.full_clean()
        return super().save(*args, **kwargs)

    def clean(self):
        obj = self
        parents = set()
        while obj is not None:
            if obj in parents:
                raise ValidationError('Loop error', code='infinite_loop')
            parents.add(obj)
            obj = obj.parent

Leave a comment