I have next models:
class Target(models.Model):
name = models.CharField(max_length=100, blank=False)
class SubTarget(models.Model):
target = models.ForeignKey(Target, related_name='sub_targets')
name = models.CharField(max_length=100, blank=True, null=True, default='')
For example, I run next code:
target = Target(name='test-target')
target.save()
sub_target = SubTarget(name='test-sub-target, target=target)
sub_target.save()
So now I have sub_target object with foreign key.
My serializer for target looks like:
class TargetSerializer(serializers.ModelSerializer):
class Meta:
model = Target
fields = ('id', 'name', 'sub_targets')
depth = 1
read_only_fields = ('sub_targets',)
and appropriate view:
class TargetDetail(generics.RetrieveUpdateDestroyAPIView):
model = Target
serializer_class = TargetSerializer
So, nothing prevents me from deletion just target created object with foreign key. Moreover, this operation delete also related sub_target object. How can I avoid this behaviour ?
I'm not sure, but I think you're asking how to prevent SubTarget objects from being deleted when you delete Target objects. By default Django emulates ON DELETE CASCADE. You can control this behavior with the on_delete key word.
So:
class Target(models.Model):
name = models.CharField(max_length=100, blank=False)
class SubTarget(models.Model):
target = models.ForeignKey(Target, related_name='sub_targets',
null=True, on_delete=models.SET_NULL)
name = models.CharField(max_length=100, blank=True, null=True, default='')
Documentation
After looking for a way to check if a model instance can be deleted in django , i came across many sample, but was not working as expected. Hope this solution can help.
Let start by creating an Abstract model class which can be inherited by other model
class ModelIsDeletable(models.Model):
name = models.CharField(max_length=200, blank=True, null=True, unique=True)
description = models.CharField(max_length=200, blank=True, null=True)
date_modified = models.DateTimeField(auto_now_add=True)
def is_deletable(self):
# get all the related object
for rel in self._meta.get_fields():
try:
# check if there is a relationship with at least one related object
related = rel.related_model.objects.filter(**{rel.field.name: self})
if related.exists():
# if there is return a Tuple of flag = False the related_model object
return False, related
except AttributeError: # an attribute error for field occurs when checking for AutoField
pass # just pass as we dont need to check for AutoField
return True, None
class Meta:
abstract = True
Example
So let say we have three model Organization and Department and StaffType
So many Department can be in an Organization
And an Organization has a particular StaffType
class StaffType(ModelIsDeletable):
pensionable = models.BooleanField(default=False)
class Organization(ModelIsDeletable):
staff_type = models.ForeignKey(to=StaffType)
class Department(ModelIsDeletable):
organization = models.ForeignKey(to=Organization, to_field="id")
so let say after adding some information you want to remove an organization model instance
that is already tied to a Department
For instance we have
Organization Table => (name = Engineering, pk = 1)
Department Table => (name=Developer, organization_fk=1, pk=1)
Now when you try to delete an organization after get it with the pk
a_org = Organization.objects.get(pk=1)
With this at hand you can check if it deletable
deletable, related_obj = a_org.is_deletable()
if not deletable:
# do some stuff with the related_obj list
else:
# call the delete function
a_org.delete()
You can override the model's delete operation like:
class Target(models.Model):
name = models.CharField(max_length=100, blank=False)
def delete(self, *args, **kwargs):
for robject in self._meta.get_all_related_objects():
if robject is not None:
q = Q(**{"%s__id" % robject.field.name: self.id})
if robject.model.objects.filter(q).exists():
raise Exception("Item has active reference.")
try:
with transaction.atomic():
super(Target, self).delete(*args, **kwargs)
except Exception, exp:
raise exp
Note that it does not work if related object has no reverse relation to the other one.
Maybe you can try another way, I just got it for my project, using Django 1.8
instance = get_object_or_404(MyModel, pk=pk)
eliminate = True
for robject in instance._meta.get_all_related_objects():
if robject is not None:
if robject.related_model.objects.filter(**{robject.field.name: instance}).exists() and eliminate:
eliminate = False
if eliminate:
instance.delete()
# additional code
else:
# additional code
pass
Late reply but this can also be avoided using model.PROTECT
on the ForeignKey
target = models.ForeignKey(
Target, related_name='sub_targets', on_delete=models.PROTECT
)
class BaseModel(models.Model):
def can_delete(self):
# get all the related object to be deleted
for related in get_candidate_relations_to_delete(self._meta):
field = related.field
if field.remote_field.on_delete == models.PROTECT:
# check for relationship with at least one related object
related = related.related_model.objects.filter(**{related.field.name: self})
if related.exists():
return False, related
return True, None
class Meta:
abstract = True