elsadek
elsadek

Reputation: 1086

Django-admin set foreign key to Null for inlines selected item instead of delete

I Have a model AB that holds two foreign keys A_id and B_id.

class AB(models.Model):
    A_id = models.ForeignKey('A')
    B_id = models.ForeignKey('B')
    field_1 = models.CharField(max_length=200, blank=True)
    field_2 = models.CharField(max_length=200, blank=True)

When editing A or B, AB items are edited inlines, what I want to achieve is that when editing let's say B I want to keep the selected AB items and set the foreign key B_id to null instead of deleting them.

thanks for any hint

Upvotes: 2

Views: 1473

Answers (3)

tamarabyte
tamarabyte

Reputation: 1297

You can use a custom inline form set and override the delete_existing method which is available in django 1.11+.

from django.forms.models import BaseInlineFormSet
from django.db import models
from django.contrib import admin

class Publisher(models.Model):
    pass

class Book(models.Model):
    publisher = models.ForeignKey(Publisher, null=True)

class CustomInlineFormSet(BaseInlineFormSet):
def delete_existing(self, obj, commit=True):
    """Unhook a model instead of deleting it."""
    if commit:
        obj.publisher = None
        obj.save()

class BooktInline(admin.TabularInline):
    formset = CustomInlineFormSet

This changes it so that the 'delete' action on admin inline formsets will unhook the inline model instead of deleting it.

Upvotes: 2

Adam
Adam

Reputation: 968

I wound up here because I had the same question. I think the previous answer misses the issue here -- the use case here is the user checks the "delete" checkbox on an InlineModelAdmin, not that they delete the model linked by the foreign key.

I think you can simplify the original problem, consider just that model B has a nullable foreign key to model A:

class A(models.Model):
    pass

class B(models.Model):
    linked_a = models.ForeignKey(A, null=True)

Then the admin lists each B linked to an A using an inline:

class BInline(TabularInline):
    model = B

class AModelAdmin(ModelAdmin):
    inlines = [BInline]

The question is, is there a way to make the "delete" checkbox on BInline result in B.linked_a = None rather than deleting the instance of B?

The reason this seems like a logical operation is that if you used a ManyToManyField to join these two objects, that's what would happen -- it wouldn't delete B, it would just "unlink" it.

Unfortunately, the answer as far as I can tell is that you can't do this easily. In both cases the inline is showing a database row, but while the inline for a ForeignKey is showing the related object itself, the inline for a ManyToManyField is showing a row from the join table (eg. the relationship). So in terms of database operations the "delete" action is the same, it's just that in one case you delete the related object, in the other case you just delete the relationship.

Upvotes: 3

AdelaN
AdelaN

Reputation: 3536

If I understand this correctly, what you want is protection against cascade deletion. If this is the case, you need to specify what django should do on deletion of an A or B model. From the docs:

When an object referenced by a ForeignKey is deleted, Django by default emulates the behavior of the SQL constraint ON DELETE CASCADE and also deletes the object containing the ForeignKey. This behavior can be overridden by specifying the on_delete argument. For example, if you have a nullable ForeignKey and you want it to be set null when the referenced object is deleted:

In order to set the ForeignKey null, you can do it like this:

A_id = models.ForeignKey('A', null=True, on_delete=models.SET_NULL)
B_id = models.ForeignKey('B', null=True, on_delete=models.SET_NULL)

Good luck and hope this helps.

Upvotes: 1

Related Questions