Commit a4d01e2b by Kálmán Viktor

Merge branch 'issue-vm-detail-fixes' into 'master'

Issue Vm Detail Fixes

- Rename virtual machines and update description
- Remove interfaces

🚧
parents 1be19049 44aed6e2
...@@ -187,7 +187,7 @@ html { ...@@ -187,7 +187,7 @@ html {
height: 300px; height: 300px;
} }
#vm-details-rename, #vm-details-rename *, #vm-details-h1-name, #vm-list-rename, #vm-list-rename *, #vm-details-rename, #vm-details-h1-name, #vm-details-rename ,
#node-details-rename, #node-details-rename *, #node-details-h1-name, #node-list-rename, #node-list-rename *#group-details-rename, #group-details-rename *, #group-details-h1-name, #group-list-rename, #group-list-rename * { #node-details-rename, #node-details-rename *, #node-details-h1-name, #node-list-rename, #node-list-rename *#group-details-rename, #group-details-rename *, #group-details-h1-name, #group-list-rename, #group-list-rename * {
display: inline; display: inline;
...@@ -197,7 +197,11 @@ html { ...@@ -197,7 +197,11 @@ html {
display: none; display: none;
} }
#vm-details-rename-name, #node-details-rename-name, #group-details-rename-name { .vm-details-home-name {
max-width: 401px;
}
#node-details-rename-name, #group-details-rename-name {
max-width: 160px; max-width: 160px;
} }
...@@ -467,3 +471,23 @@ footer a, footer a:hover, footer a:visited { ...@@ -467,3 +471,23 @@ footer a, footer a:hover, footer a:visited {
overflow: hidden; overflow: hidden;
padding-left: 10px; padding-left: 10px;
} }
#vm-details-home-description {
display: inline-block;
position: relative;
}
#vm-details-home-description textarea {
min-width: 240px;
min-height: 250px;
}
.vm-details-home-edit-description-click, .vm-details-home-edit-name-click {
cursor: pointer;
}
.vm-details-description-submit {
position: absolute;
bottom: 10px;
right: 20px;
}
...@@ -303,7 +303,8 @@ function deleteObject(data) { ...@@ -303,7 +303,8 @@ function deleteObject(data) {
// no need to remove them from DOM // no need to remove them from DOM
$('a[data-disk-pk="' + data.pk + '"]').parent("li").fadeOut(); $('a[data-disk-pk="' + data.pk + '"]').parent("li").fadeOut();
$('a[data-disk-pk="' + data.pk + '"]').parent("h4").fadeOut(); $('a[data-disk-pk="' + data.pk + '"]').parent("h4").fadeOut();
} else { }
else {
$('a[data-'+data['type']+'-pk="' + data['pk'] + '"]').closest('tr').fadeOut(function() { $('a[data-'+data['type']+'-pk="' + data['pk'] + '"]').closest('tr').fadeOut(function() {
$(this).remove(); $(this).remove();
}); });
......
...@@ -31,33 +31,6 @@ $(function() { ...@@ -31,33 +31,6 @@ $(function() {
return false; return false;
}); });
/* rename */
$("#vm-details-h1-name, .vm-details-rename-button").click(function() {
$("#vm-details-h1-name").hide();
$("#vm-details-rename").css('display', 'inline');
$("#vm-details-rename-name").focus();
});
/* rename ajax */
$('#vm-details-rename-submit').click(function() {
var name = $('#vm-details-rename-name').val();
$.ajax({
method: 'POST',
url: location.href,
data: {'new_name': name},
headers: {"X-CSRFToken": getCookie('csrftoken')},
success: function(data, textStatus, xhr) {
$("#vm-details-h1-name").html(data['new_name']).show();
$('#vm-details-rename').hide();
// addMessage(data['message'], "success");
},
error: function(xhr, textStatus, error) {
addMessage("Error during renaming!", "danger");
}
});
return false;
});
/* remove tag */ /* remove tag */
$('.vm-details-remove-tag').click(function() { $('.vm-details-remove-tag').click(function() {
var to_remove = $.trim($(this).parent('div').text()); var to_remove = $.trim($(this).parent('div').text());
...@@ -150,8 +123,7 @@ $(function() { ...@@ -150,8 +123,7 @@ $(function() {
/* add network button */ /* add network button */
$("#vm-details-network-add").click(function() { $("#vm-details-network-add").click(function() {
$("#vm-details-network-add-for-form").html($("#vm-details-network-add-form").html()); $("#vm-details-network-add-form").toggle();
$('input[name="new_network_managed"]').tooltip();
return false; return false;
}); });
...@@ -165,6 +137,126 @@ $(function() { ...@@ -165,6 +137,126 @@ $(function() {
$(".vm-details-help-button").click(function() { $(".vm-details-help-button").click(function() {
$(".vm-details-help").stop().slideToggle(); $(".vm-details-help").stop().slideToggle();
}); });
/* for interface remove buttons */
$('.interface-remove').click(function() {
var interface_pk = $(this).data('interface-pk');
addModalConfirmation(removeInterface,
{ 'url': '/dashboard/interface/' + interface_pk + '/delete/',
'data': [],
'pk': interface_pk,
'type': "interface",
});
return false;
});
/* removing interface post */
function removeInterface(data) {
$.ajax({
type: 'POST',
url: data['url'],
headers: {"X-CSRFToken": getCookie('csrftoken')},
success: function(re, textStatus, xhr) {
/* remove the html element */
$('a[data-interface-pk="' + data.pk + '"]').closest("div").fadeOut();
/* add the removed element to the list */
network_select = $('select[name="new_network_vlan"]');
name_html = (re.removed_network.managed ? "": "") + " " + re.removed_network.vlan;
option_html = '<option value="' + re.removed_network.vlan_pk + '">' + name_html + '</option>';
// if it's -1 then it's a dummy placeholder so we can use .html
if($("option", network_select)[0].value === "-1") {
network_select.html(option_html);
network_select.next("div").children("button").prop("disabled", false);
} else {
network_select.append(option_html);
}
},
error: function(xhr, textStatus, error) {
addMessage('Uh oh :(', 'danger')
}
});
}
/* rename */
$("#vm-details-h1-name, .vm-details-rename-button").click(function() {
$("#vm-details-h1-name").hide();
$("#vm-details-rename").css('display', 'inline');
$("#vm-details-rename-name").focus();
});
/* rename in home tab */
$(".vm-details-home-edit-name-click").click(function() {
$(".vm-details-home-edit-name-click").hide();
$("#vm-details-home-rename").show();
$("input", $("#vm-details-home-rename")).focus();
});
/* rename ajax */
$('.vm-details-rename-submit').click(function() {
var name = $(this).parent("span").prev("input").val();
$.ajax({
method: 'POST',
url: location.href,
data: {'new_name': name},
headers: {"X-CSRFToken": getCookie('csrftoken')},
success: function(data, textStatus, xhr) {
$(".vm-details-home-edit-name").text(data['new_name']).show();
$(".vm-details-home-edit-name").parent("div").show();
$(".vm-details-home-edit-name-click").show();
$(".vm-details-home-rename-form-div").hide();
// update the inputs too
$(".vm-details-rename-submit").parent("span").prev("input").val(data['new_name']);
},
error: function(xhr, textStatus, error) {
addMessage("Error during renaming!", "danger");
}
});
return false;
});
/* update description click */
$(".vm-details-home-edit-description-click").click(function() {
$(".vm-details-home-edit-description-click").hide();
$("#vm-details-home-description").show();
return false;
});
/* description update ajax */
$('.vm-details-description-submit').click(function() {
var description = $(this).prev("textarea").val();
$.ajax({
method: 'POST',
url: location.href,
data: {'new_description': description},
headers: {"X-CSRFToken": getCookie('csrftoken')},
success: function(data, textStatus, xhr) {
var new_desc = data['new_description'];
/* we can't simply use $.text, because we need new lines */
var tagsToReplace = {
'&': "&amp;",
'<': "&lt;",
'>': "&gt;",
};
new_desc = new_desc.replace(/[&<>]/g, function(tag) {
return tagsToReplace[tag] || tag;
});
$(".vm-details-home-edit-description")
.html(new_desc.replace(/\n/g, "<br />"));
$(".vm-details-home-edit-description-click").show();
$("#vm-details-home-description").hide();
// update the textareia
$("vm-details-home-description textarea").text(data['new_description']);
},
error: function(xhr, textStatus, error) {
addMessage("Error during renaming!", "danger");
}
});
return false;
});
}); });
......
...@@ -10,14 +10,18 @@ ...@@ -10,14 +10,18 @@
{% include "dashboard/vm-detail/_operations.html" %} {% include "dashboard/vm-detail/_operations.html" %}
</div> </div>
<h1> <h1>
<div id="vm-details-rename"> <div id="vm-details-rename" class="vm-details-home-rename-form-div">
<form action="" method="POST" id="vm-details-rename-form"> <form action="" method="POST" id="vm-details-rename-form">
{% csrf_token %} {% csrf_token %}
<input id="vm-details-rename-name" class="form-control" name="new_name" type="text" value="{{ instance.name }}"/> <div class="input-group vm-details-home-name">
<button type="submit" id="vm-details-rename-submit" class="btn">{% trans "Rename" %}</button> <input id="vm-details-rename-name" class="form-control input-sm" name="new_name" type="text" value="{{ instance.name }}"/>
<span class="input-group-btn">
<button type="submit" class="btn btn-sm vm-details-rename-submit">{% trans "Rename" %}</button>
</span>
</div>
</form> </form>
</div> </div>
<div id="vm-details-h1-name"> <div id="vm-details-h1-name" class="vm-details-home-edit-name">
{{ instance.name }} {{ instance.name }}
</div> </div>
<small>{{ instance.primary_host.get_fqdn }}</small> <small>{{ instance.primary_host.get_fqdn }}</small>
......
...@@ -4,8 +4,46 @@ ...@@ -4,8 +4,46 @@
<dl> <dl>
<dt>{% trans "System" %}:</dt> <dt>{% trans "System" %}:</dt>
<dd><i class="icon-{{ os_type_icon }}"></i> {{ instance.system }}</dd> <dd><i class="icon-{{ os_type_icon }}"></i> {{ instance.system }}</dd>
<dt style="margin-top: 5px;">{% trans "Description" %}:</dt> <dt style="margin-top: 5px;">
<dd><small>{{ instance.description }}</small></dd> {% trans "Name" %}:
<a href="#" class="vm-details-home-edit-name-click"><i class="icon-pencil"></i></a>
</dt>
<dd>
<div class="vm-details-home-edit-name-click">
<small class="vm-details-home-edit-name">{{ instance.name }}</small>
</div>
<div class="js-hidden vm-details-home-rename-form-div" id="vm-details-home-rename">
<form method="POST">
{% csrf_token %}
<div class="input-group">
<input type="text" name="new_name" value="{{ instance.name }}" class="form-control input-sm"/>
<span class="input-group-btn">
<button type="submit" class="btn btn-success btn-sm vm-details-rename-submit">
<i class="icon-pencil"></i> {% trans "Rename" %}
</button>
</span>
</div>
</form>
</div>
</dd>
<dt style="margin-top: 5px;">
{% trans "Description" %}:
<a href="#" class="vm-details-home-edit-description-click"><i class="icon-pencil"></i></a>
</dt>
<dd>
{% csrf_token %}
<div class="vm-details-home-edit-description-click">
<small class="vm-details-home-edit-description">{{ instance.description|linebreaks }}</small>
</div>
<div id="vm-details-home-description" class="js-hidden">
<form method="POST">
<textarea name="new_description" class="form-control">{{ instance.description }}</textarea>
<button type="submit" class="btn btn-xs btn-success vm-details-description-submit">
<i class="icon-pencil"></i> {% trans "Update" %}
</button>
</form>
</div>
</dd>
</dl> </dl>
<h4>{% trans "Expiration" %} {% if instance.is_expiring %}<i class="icon-warning-sign text-danger"></i>{% endif %} <h4>{% trans "Expiration" %} {% if instance.is_expiring %}<i class="icon-warning-sign text-danger"></i>{% endif %}
......
...@@ -6,13 +6,50 @@ ...@@ -6,13 +6,50 @@
{% trans "Interfaces" %} {% trans "Interfaces" %}
</h2> </h2>
<div class="row" id="vm-details-network-add-for-form"> <div class="js-hidden row" id="vm-details-network-add-form">
<div class="col-md-12">
<div>
<hr />
<h3>
{% trans "Add new network interface!" %}
</h3>
<form method="POST" action="">
{% csrf_token %}
<div class="input-group" style="max-width: 330px;">
<select name="new_network_vlan" class="form-control font-awesome-font">
{% for v in vlans %}
<option value="{{ v.pk }}">
{% if v.managed %}
&#xf0ac;
{% else %}
&#xf0c1;
{% endif %}
{{ v.name }}
</option>
{% empty %}
<option value="-1">No more networks!</option>
{% endfor %}
</select>
<div class="input-group-btn">
<button {% if vlans|length == 0 %}disabled{% endif %}
type="submit" class="btn btn-success"><i class="icon-plus-sign"></i></button>
</div>
</div>
</form>
<hr />
</div>
</div>
</div> </div>
{% for i in instance.interface_set.all %} {% for i in instance.interface_set.all %}
<div>
<h3 class="list-group-item-heading dashboard-vm-details-network-h3"> <h3 class="list-group-item-heading dashboard-vm-details-network-h3">
<i class="icon-{% if i.host %}globe{% else %}link{% endif %}"></i> {{ i.vlan.name }} <i class="icon-{% if i.host %}globe{% else %}link{% endif %}"></i> {{ i.vlan.name }}
{% if not i.host %}(unmanaged) <a href="#" class="btn btn-danger btn-xs">{% trans "remove" %}</a>{% endif %} {% if not i.host%}({% trans "unmanaged" %}){% endif %}
<a href="{% url "dashboard.views.interface-delete" pk=i.pk %}?next={{ request.path }}" class="btn btn-danger btn-xs interface-remove"
data-interface-pk="{{ i.pk }}">
{% trans "remove" %}
</a>
</h3> </h3>
{% if i.host %} {% if i.host %}
<div class="row"> <div class="row">
...@@ -109,31 +146,5 @@ ...@@ -109,31 +146,5 @@
</div> </div>
</div> </div>
{% endif %} {% endif %}
{% endfor %}
<div class="js-hidden row" id="vm-details-network-add-form">
<div class="col-md-12">
<div>
<hr />
<h3>
{% trans "Add new network interface!" %}
</h3>
<form method="POST" action="">
{% csrf_token %}
<div class="input-group" style="max-width: 330px;">
<select name="new_network_vlan" class="form-control">
{% if vlans|length == 0 %}
<option value="-1">No more networks!</option>
{% endif %}
{% for v in vlans %}
<option value="{{ v.pk }}">{{ v.name }}</option>
{% endfor %}
</select>
<div class="input-group-btn">
<button type="submit" class="btn btn-success"><i class="icon-plus-sign"></i></button>
</div>
</div>
</form>
<hr />
</div>
</div>
</div> </div>
{% endfor %}
import json
from unittest import skip from unittest import skip
from django.test import TestCase from django.test import TestCase
from django.test.client import Client from django.test.client import Client
...@@ -174,6 +176,46 @@ class VmDetailTest(LoginMixin, TestCase): ...@@ -174,6 +176,46 @@ class VmDetailTest(LoginMixin, TestCase):
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
self.assertEqual(inst.interface_set.count(), interface_count + 1) self.assertEqual(inst.interface_set.count(), interface_count + 1)
def test_permitted_network_delete(self):
c = Client()
self.login(c, "user1")
inst = Instance.objects.get(pk=1)
inst.set_level(self.u1, 'owner')
inst.add_interface(vlan=Vlan.objects.get(pk=1), user=self.us)
iface_count = inst.interface_set.count()
c.post("/dashboard/interface/1/delete/")
self.assertEqual(inst.interface_set.count(), iface_count - 1)
def test_permitted_network_delete_w_ajax(self):
c = Client()
self.login(c, "user1")
inst = Instance.objects.get(pk=1)
inst.set_level(self.u1, 'owner')
vlan = Vlan.objects.get(pk=1)
inst.add_interface(vlan=vlan, user=self.us)
iface_count = inst.interface_set.count()
response = c.post("/dashboard/interface/1/delete/",
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
removed_network = json.loads(response.content)['removed_network']
self.assertEqual(removed_network['vlan'], vlan.name)
self.assertEqual(removed_network['vlan_pk'], vlan.pk)
self.assertEqual(removed_network['managed'], vlan.managed)
self.assertEqual(inst.interface_set.count(), iface_count - 1)
def test_unpermitted_network_delete(self):
c = Client()
self.login(c, "user1")
inst = Instance.objects.get(pk=1)
inst.set_level(self.u1, 'user')
inst.add_interface(vlan=Vlan.objects.get(pk=1), user=self.us)
iface_count = inst.interface_set.count()
response = c.post("/dashboard/interface/1/delete/")
self.assertEqual(iface_count, inst.interface_set.count())
self.assertEqual(response.status_code, 403)
def test_create_vm_w_unpermitted_network(self): def test_create_vm_w_unpermitted_network(self):
c = Client() c = Client()
self.login(c, 'user2') self.login(c, 'user2')
...@@ -559,6 +601,41 @@ class VmDetailTest(LoginMixin, TestCase): ...@@ -559,6 +601,41 @@ class VmDetailTest(LoginMixin, TestCase):
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
self.assertEqual(instance_count + 2, Instance.objects.all().count()) self.assertEqual(instance_count + 2, Instance.objects.all().count())
def test_unpermitted_description_update(self):
c = Client()
self.login(c, "user1")
inst = Instance.objects.get(pk=1)
inst.set_level(self.u2, 'owner')
inst.set_level(self.u1, 'user')
old_desc = inst.description
response = c.post("/dashboard/vm/1/", {'new_description': 'test1234'})
self.assertEqual(response.status_code, 403)
self.assertEqual(Instance.objects.get(pk=1).description, old_desc)
def test_permitted_description_update_w_ajax(self):
c = Client()
self.login(c, "user1")
inst = Instance.objects.get(pk=1)
inst.set_level(self.u1, 'owner')
response = c.post("/dashboard/vm/1/", {'new_description': "naonyo"},
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(response.status_code, 200)
self.assertEqual(json.loads(response.content)['new_description'],
"naonyo")
self.assertEqual(Instance.objects.get(pk=1).description, "naonyo")
def test_permitted_description_update(self):
c = Client()
self.login(c, "user1")
inst = Instance.objects.get(pk=1)
inst.set_level(self.u1, 'owner')
response = c.post("/dashboard/vm/1/", {'new_description': "naonyo"})
self.assertEqual(response.status_code, 302)
self.assertEqual(Instance.objects.get(pk=1).description, "naonyo")
class NodeDetailTest(LoginMixin, TestCase): class NodeDetailTest(LoginMixin, TestCase):
fixtures = ['test-vm-fixture.json', 'node.json'] fixtures = ['test-vm-fixture.json', 'node.json']
......
...@@ -12,7 +12,7 @@ from .views import ( ...@@ -12,7 +12,7 @@ from .views import (
TemplateDelete, TemplateDetail, TemplateList, TransferOwnershipConfirmView, TemplateDelete, TemplateDetail, TemplateList, TransferOwnershipConfirmView,
TransferOwnershipView, vm_activity, VmCreate, VmDelete, VmDetailView, TransferOwnershipView, vm_activity, VmCreate, VmDelete, VmDetailView,
VmDetailVncTokenView, VmGraphView, VmList, VmMassDelete, VmMigrateView, VmDetailVncTokenView, VmGraphView, VmList, VmMassDelete, VmMigrateView,
VmRenewView, DiskRemoveView, get_disk_download_status, VmRenewView, DiskRemoveView, get_disk_download_status, InterfaceDeleteView,
) )
urlpatterns = patterns( urlpatterns = patterns(
...@@ -108,6 +108,9 @@ urlpatterns = patterns( ...@@ -108,6 +108,9 @@ urlpatterns = patterns(
url(r'^disk/(?P<pk>\d+)/status/$', get_disk_download_status, url(r'^disk/(?P<pk>\d+)/status/$', get_disk_download_status,
name="dashboard.views.disk-status"), name="dashboard.views.disk-status"),
url(r'^interface/(?P<pk>\d+)/delete/$', InterfaceDeleteView.as_view(),
name="dashboard.views.interface-delete"),
url(r'^profile/$', MyPreferencesView.as_view(), url(r'^profile/$', MyPreferencesView.as_view(),
name="dashboard.views.profile"), name="dashboard.views.profile"),
) )
...@@ -216,7 +216,7 @@ class VmDetailView(CheckedDetailView): ...@@ -216,7 +216,7 @@ class VmDetailView(CheckedDetailView):
context['vlans'] = Vlan.get_objects_with_level( context['vlans'] = Vlan.get_objects_with_level(
'user', self.request.user 'user', self.request.user
).exclude( ).exclude( # exclude already added interfaces
pk__in=Interface.objects.filter( pk__in=Interface.objects.filter(
instance=self.get_object()).values_list("vlan", flat=True) instance=self.get_object()).values_list("vlan", flat=True)
).all() ).all()
...@@ -239,6 +239,7 @@ class VmDetailView(CheckedDetailView): ...@@ -239,6 +239,7 @@ class VmDetailView(CheckedDetailView):
options = { options = {
'change_password': self.__change_password, 'change_password': self.__change_password,
'new_name': self.__set_name, 'new_name': self.__set_name,
'new_description': self.__set_description,
'new_tag': self.__add_tag, 'new_tag': self.__add_tag,
'to_remove': self.__remove_tag, 'to_remove': self.__remove_tag,
'port': self.__add_port, 'port': self.__add_port,
...@@ -309,8 +310,30 @@ class VmDetailView(CheckedDetailView): ...@@ -309,8 +310,30 @@ class VmDetailView(CheckedDetailView):
) )
else: else:
messages.success(request, success_message) messages.success(request, success_message)
return redirect(reverse_lazy("dashboard.views.detail", return redirect(self.object.get_absolute_url())
kwargs={'pk': self.object.pk}))
def __set_description(self, request):
self.object = self.get_object()
if not self.object.has_level(request.user, 'owner'):
raise PermissionDenied()
new_description = request.POST.get("new_description")
Instance.objects.filter(pk=self.object.pk).update(
**{'description': new_description})
success_message = _("VM description successfully updated!")
if request.is_ajax():
response = {
'message': success_message,
'new_description': new_description,
}
return HttpResponse(
json.dumps(response),
content_type="application/json"
)
else:
messages.success(request, success_message)
return redirect(self.object.get_absolute_url())
def __add_tag(self, request): def __add_tag(self, request):
new_tag = request.POST.get('new_tag') new_tag = request.POST.get('new_tag')
...@@ -393,7 +416,7 @@ class VmDetailView(CheckedDetailView): ...@@ -393,7 +416,7 @@ class VmDetailView(CheckedDetailView):
if not self.object.has_level(request.user, 'owner'): if not self.object.has_level(request.user, 'owner'):
raise PermissionDenied() raise PermissionDenied()
vlan = Vlan.objects.get(pk=request.POST.get("new_network_vlan")) vlan = get_object_or_404(Vlan, pk=request.POST.get("new_network_vlan"))
if not vlan.has_level(request.user, 'user'): if not vlan.has_level(request.user, 'user'):
raise PermissionDenied() raise PermissionDenied()
try: try:
...@@ -2260,3 +2283,53 @@ class InstanceActivityDetail(SuperuserRequiredMixin, DetailView): ...@@ -2260,3 +2283,53 @@ class InstanceActivityDetail(SuperuserRequiredMixin, DetailView):
order_by('-started').select_related('user'). order_by('-started').select_related('user').
prefetch_related('children')) prefetch_related('children'))
return ctx return ctx
class InterfaceDeleteView(DeleteView):
model = Interface
def get_template_names(self):
if self.request.is_ajax():
return ['dashboard/confirm/ajax-delete.html']
else:
return ['dashboard/confirm/base-delete.html']
def get_context_data(self, **kwargs):
context = super(InterfaceDeleteView, self).get_context_data(**kwargs)
interface = self.get_object()
context['text'] = _("Are you sure you want to remove this interface "
"from <strong>%(vm)s</strong>?" %
{'vm': interface.instance.name})
return context
def delete(self, request, *args, **kwargs):
self.object = self.get_object()
instance = self.object.instance
if not instance.has_level(request.user, "owner"):
raise PermissionDenied()
instance.remove_interface(interface=self.object, user=request.user)
success_url = self.get_success_url()
success_message = _("Interface successfully deleted!")
if request.is_ajax():
return HttpResponse(
json.dumps(
{'message': success_message,
'removed_network': {
'vlan': self.object.vlan.name,
'vlan_pk': self.object.vlan.pk,
'managed': self.object.host is not None,
}}),
content_type="application/json",
)
else:
messages.success(request, success_message)
return HttpResponseRedirect("%s#network" % success_url)
def get_success_url(self):
redirect = self.request.POST.get("next")
if redirect:
return redirect
self.object.instance.get_absolute_url()
...@@ -53,6 +53,7 @@ class Interface(Model): ...@@ -53,6 +53,7 @@ class Interface(Model):
class Meta: class Meta:
app_label = 'vm' app_label = 'vm'
db_table = 'vm_interface' db_table = 'vm_interface'
ordering = ("-vlan__managed", )
def __unicode__(self): def __unicode__(self):
return 'cloud-' + str(self.instance.id) + '-' + str(self.vlan.vid) return 'cloud-' + str(self.instance.id) + '-' + str(self.vlan.vid)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment