diff --git a/circle/dashboard/forms.py b/circle/dashboard/forms.py
index 1bf863b..af2489e 100644
--- a/circle/dashboard/forms.py
+++ b/circle/dashboard/forms.py
@@ -41,6 +41,7 @@ from django.forms.widgets import TextInput, HiddenInput
 from django.template import Context
 from django.template.loader import render_to_string
 from django.utils.html import escape, format_html
+from django.utils.safestring import mark_safe
 from django.utils.translation import ugettext_lazy as _
 from sizefield.widgets import FileSizeWidget
 from django.core.urlresolvers import reverse_lazy
@@ -951,18 +952,45 @@ class VmAddInterfaceForm(OperationForm):
         self.fields['vlan'] = field
 
 
+class DeployChoiceField(forms.ModelChoiceField):
+    def __init__(self, *args, **kwargs):
+        self.instance = kwargs.pop("instance")
+        super(DeployChoiceField, self).__init__(*args, **kwargs)
+
+    def label_from_instance(self, obj):
+        traits = set(obj.traits.all())
+        req_traits = set(self.instance.req_traits.all())
+        # if the subset is empty the node satisfies the required traits
+        subset = req_traits - traits
+
+        label = "%s %s" % (
+            "&#xf071" if subset else "", escape(obj.name),
+        )
+
+        if subset:
+            missing_traits = ", ".join(map(lambda x: escape(x.name), subset))
+            label += _(" (missing_traits: %s)") % missing_traits
+
+        return mark_safe(label)
+
+
 class VmDeployForm(OperationForm):
 
     def __init__(self, *args, **kwargs):
         choices = kwargs.pop('choices', None)
+        instance = kwargs.pop("instance")
 
         super(VmDeployForm, self).__init__(*args, **kwargs)
 
         if choices is not None:
-            self.fields.insert(0, 'node', forms.ModelChoiceField(
+            self.fields.insert(0, 'node', DeployChoiceField(
                 queryset=choices, required=False, label=_('Node'), help_text=_(
                     "Deploy virtual machine to this node "
-                    "(blank allows scheduling automatically).")))
+                    "(blank allows scheduling automatically)."),
+                widget=forms.Select(attrs={
+                    'class': "font-awesome-font",
+                }), instance=instance
+            ))
 
 
 class VmPortRemoveForm(OperationForm):
diff --git a/circle/dashboard/templates/dashboard/_vm-migrate.html b/circle/dashboard/templates/dashboard/_vm-migrate.html
index c8024c4..718d0a3 100644
--- a/circle/dashboard/templates/dashboard/_vm-migrate.html
+++ b/circle/dashboard/templates/dashboard/_vm-migrate.html
@@ -23,11 +23,35 @@ Choose a compute node to migrate {{obj}} to.
             <i class="fa {{n.get_status_icon}}"></i> {{n.get_status_display}}</div>
           {% if current == n.pk %}<div class="label label-info">{% trans "current" %}</div>{% endif %}
           {% if recommended == n.pk %}<div class="label label-success">{% trans "recommended" %}</div>{% endif %}
+          {% if n.pk not in nodes_w_traits %}
+            <div class="label label-warning">
+              <i class="fa fa-warning"></i>
+              {% trans "missing traits" %}</div>
+          {% endif %}
         </label>
         <input id="migrate-to-{{n.pk}}" type="radio" name="to_node" value="{{ n.pk }}" style="float: right;"
           {% if current == n.pk %}disabled="disabled"{% endif %}
-          {% if recommended == n.pk %}checked="checked"{% endif %}
+          {% if recommended == n.pk and n.pk != current %}checked="checked"{% endif %}
         />
+        {% if n.pk not in nodes_w_traits %}
+          <span class="vm-migrate-node-property">
+            {% trans "Node traits" %}:
+            {% if n.traits.all %}
+              {{ n.traits.all|join:", " }}
+            {% else %}
+              -
+            {% endif %}
+          </span>
+          <span class="vm-migrate-node-property">
+            {% trans "Required traits" %}:
+            {% if object.req_traits.all %}
+              {{ object.req_traits.all|join:", " }}
+            {% else %}
+              -
+            {% endif %}
+          </span>
+          <hr />
+        {% endif %}
         <span class="vm-migrate-node-property">{% trans "CPU load" %}: {{ n.cpu_usage }}</span>
         <span class="vm-migrate-node-property">
           {% trans "RAM usage" %}: {{ n.byte_ram_usage|filesize }}/{{ n.ram_size|filesize }}</span>
diff --git a/circle/dashboard/templates/dashboard/node-detail.html b/circle/dashboard/templates/dashboard/node-detail.html
index 586cd74..c4d04ef 100644
--- a/circle/dashboard/templates/dashboard/node-detail.html
+++ b/circle/dashboard/templates/dashboard/node-detail.html
@@ -73,7 +73,7 @@
             </a>
           </li>
           <li>
-            <a href="{% url "dashboard.views.vm-list" %}?s=node:{{ node.name }}" 
+            <a href="{% url "dashboard.views.vm-list" %}?s=node_exact:{{ node.name }}"
               target="blank" class="text-center">
               <i class="fa fa-desktop fa-2x"></i><br>
               {% trans "Virtual Machines" %}
diff --git a/circle/dashboard/views/node.py b/circle/dashboard/views/node.py
index 269eb96..19bcbe4 100644
--- a/circle/dashboard/views/node.py
+++ b/circle/dashboard/views/node.py
@@ -143,8 +143,13 @@ class NodeDetailView(LoginRequiredMixin,
     def __remove_trait(self, request):
         try:
             to_remove = request.POST.get('to_remove')
-            self.object = self.get_object()
-            self.object.traits.remove(to_remove)
+            trait = Trait.objects.get(pk=to_remove)
+            node = self.get_object()
+            node.traits.remove(to_remove)
+
+            if not trait.in_use:
+                trait.delete()
+
             message = u"Success"
         except:  # note this won't really happen
             message = u"Not success"
@@ -155,7 +160,7 @@ class NodeDetailView(LoginRequiredMixin,
                 content_type="application/json"
             )
         else:
-            return redirect(self.object.get_absolute_url())
+            return redirect(node.get_absolute_url())
 
 
 class NodeList(LoginRequiredMixin, GraphMixin, SingleTableView):
diff --git a/circle/dashboard/views/vm.py b/circle/dashboard/views/vm.py
index 2bce624..f916cbd 100644
--- a/circle/dashboard/views/vm.py
+++ b/circle/dashboard/views/vm.py
@@ -67,6 +67,7 @@ from ..forms import (
     VmRemoveInterfaceForm,
 )
 from ..models import Favourite
+from manager.scheduler import has_traits
 
 logger = logging.getLogger(__name__)
 
@@ -444,6 +445,20 @@ class VmMigrateView(FormOperationMixin, VmOperationView):
         val.update({'choices': choices, 'default': default})
         return val
 
+    def get_context_data(self, *args, **kwargs):
+        ctx = super(VmMigrateView, self).get_context_data(*args, **kwargs)
+
+        inst = self.get_object()
+        if isinstance(inst, Instance):
+            nodes_w_traits = [
+                n.pk for n in Node.objects.filter(enabled=True)
+                if n.online and
+                has_traits(inst.req_traits.all(), n)
+            ]
+            ctx['nodes_w_traits'] = nodes_w_traits
+
+        return ctx
+
 
 class VmPortRemoveView(FormOperationMixin, VmOperationView):
 
@@ -698,6 +713,7 @@ class VmDeployView(FormOperationMixin, VmOperationView):
             online = (n.pk for n in
                       Node.objects.filter(enabled=True) if n.online)
             kwargs['choices'] = Node.objects.filter(pk__in=online)
+            kwargs['instance'] = self.get_object()
         return kwargs
 
 
diff --git a/circle/vm/models/common.py b/circle/vm/models/common.py
index c86c0b1..fd3efa2 100644
--- a/circle/vm/models/common.py
+++ b/circle/vm/models/common.py
@@ -170,3 +170,10 @@ class Trait(Model):
 
     def __unicode__(self):
         return self.name
+
+    @property
+    def in_use(self):
+        return (
+            self.instance_set.exists() or self.node_set.exists()
+            or self.instancetemplate_set.exists()
+        )