La forma de "proteger" los objetos relacionados es via la definición del ForeignKey.on_delete que puede tomar los siguientes valores
CASCADE: Borrado en cascada, comportamiento por defecto.
PROTECT: Protege al objeto relacionado del borrado haciendo saltar la excepción django.db.models.ProtectedError.
SET_NULL: Asigna el valor null a la clave, sólo posible si null es True.
SET_DEFAULT: Pone la clave foránea a su valor por defecto (debe estar definido).
SET(): Asigna a la clave foránea el valor pasado en el set.
DO_NOTHING: No hace nada y deja el comportamiento por defecto de la db. (Si la hemos creado con syncdb, no hará nada)
Una práctica aconsejable en un entorno de usuario sería capturar la excepción via PROTECT y devolver un mensaje de error con la información para el usuario.Por ejemplo:
Si en la definición del modelo Factura usamos
cliente=models.ForeignKey(Cliente, on_delete=models.PROTECT)
Y ahora queremos borrar un cliente que tiene una factura asociada
try:
cliente.delete()
except django.db.models.ProtectedError, ex:
return HttpResponse( ('El cliente %s tiene al menos una factura y no puede borrarse.' % (cliente.descripcion,)), 'text/html')