1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2024-11-01 03:54:15 +00:00

Merge pull request #346 from catborise/master

fix some errors
This commit is contained in:
Anatoliy Guskov 2020-07-25 10:47:31 +03:00 committed by GitHub
commit 8e4073f4b7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
36 changed files with 16100 additions and 11347 deletions

View file

@ -335,13 +335,13 @@ python manage.py test
### Screenshots
Instance Detail:
<img src="doc/images/instance.PNG" width="95%" align="center"/>
<img src="doc/images/instance.PNG" width="96%" align="center"/>
Instance List:</br>
<img src="doc/images/grouped.PNG" width="47%"/>
<img src="doc/images/nongrouped.PNG" width="51%"/>
<img src="doc/images/grouped.PNG" width="43%"/>
<img src="doc/images/nongrouped.PNG" width="53%"/>
Other: </br>
<img src="doc/images/hosts.PNG" width="52%"/>
<img src="doc/images/log.PNG" width="47%"/>
<img src="doc/images/hosts.PNG" width="47%"/>
<img src="doc/images/log.PNG" width="49%"/>
### License

View file

@ -18,7 +18,7 @@
<div class="col-lg-12">
<h3 class="page-header">{% trans "Edit Profile" %}</h3>
{% if perms.accounts.change_password %}
<a href="{% url 'change_password' %}" class="btn btn-primary">{% icon 'lock' %} {% trans "Change Password" %}</a>
<a href="{% url 'change_password' %}" class="ml-3 btn btn-primary">{% icon 'lock' %} {% trans "Change Password" %}</a>
{% endif %}
<form method="post" action="" role="form" aria-label="Edit user info form">{% csrf_token %}
<div class="form-group">

View file

@ -1,5 +1,8 @@
from django import forms
from django.contrib.auth.models import Group, User
from django.contrib.auth.forms import ReadOnlyPasswordHashField
from django.urls import reverse_lazy
from django.utils.text import format_lazy
from django.utils.translation import ugettext_lazy as _
from accounts.models import UserAttributes
@ -68,6 +71,16 @@ class UserForm(forms.ModelForm):
'is_superuser',
]
def __init__(self, *args, **kwargs):
super(UserForm, self).__init__(*args, **kwargs)
password = ReadOnlyPasswordHashField(label=_("Password"),
help_text=format_lazy(_("""Raw passwords are not stored, so there is no way to see
this user's password, but you can change the password
using <a href='{}'>this form</a>."""),
reverse_lazy('admin:user_update_password', args=[self.instance.id,]))
)
self.fields['Password'] = password
class UserCreateForm(UserForm):
password = forms.CharField(widget=forms.PasswordInput)

View file

@ -1,5 +1,4 @@
from django.urls import path
from django.contrib.auth.views import PasswordChangeView, PasswordChangeDoneView
from . import views
@ -10,6 +9,7 @@ urlpatterns = [
path('groups/<int:pk>/delete/', views.group_delete, name='group_delete'),
path('users/', views.user_list, name='user_list'),
path('users/create/', views.user_create, name='user_create'),
path('users/<int:pk>/update_password/', views.user_update_password, name='user_update_password'),
path('users/<int:pk>/update/', views.user_update, name='user_update'),
path('users/<int:pk>/delete/', views.user_delete, name='user_delete'),
path('users/<int:pk>/block/', views.user_block, name='user_block'),

View file

@ -1,4 +1,7 @@
from django.conf import settings
from django.contrib import messages
from django.contrib.auth import update_session_auth_hash
from django.contrib.auth.forms import AdminPasswordChangeForm
from django.contrib.auth.models import Group, User
from django.core.paginator import Paginator
from django.shortcuts import get_object_or_404, redirect, render
@ -133,6 +136,29 @@ def user_update(request, pk):
},
)
@superuser_only
def user_update_password(request, pk):
user = get_object_or_404(User, pk=pk)
if request.method == 'POST':
form = AdminPasswordChangeForm(user, request.POST)
if form.is_valid():
user = form.save()
update_session_auth_hash(request, user) # Important!
messages.success(request, _('User password changed: {}'.format(user.username)))
return redirect('admin:user_list')
else:
messages.error(request, _('Wrong Data Provided'))
else:
form = AdminPasswordChangeForm(user)
return render(
request,
'accounts/change_password_form.html',
{
'form': form,
'user': user.username
}
)
@superuser_only
def user_delete(request, pk):

View file

@ -11,7 +11,6 @@
<!-- /.row -->
{% include 'errors_block.html' %}
{% include 'messages_block.html' %}
<div class="">
<div class="col-lg-12">

View file

@ -12,6 +12,7 @@ class TcpComputeForm(forms.ModelForm):
class Meta:
model = Compute
widgets = {'password': forms.PasswordInput()}
fields = '__all__'
@ -30,6 +31,7 @@ class TlsComputeForm(forms.ModelForm):
class Meta:
model = Compute
widgets = {'password': forms.PasswordInput()}
fields = '__all__'

View file

@ -25,26 +25,26 @@
<div class="col-lg-12">
<table class="table table-striped table-hover">
<thead>
<tr>
<th span="col">{% trans "Name" %}</th>
<th span="col">{% trans "Status" %}</th>
<th span="col">{% trans "Details" %}</th>
<th span="col">{% trans "Actions" %}</th>
<tr class="d-flex">
<th span="col" class="col-sm-3">{% trans "Name" %}</th>
<th span="col" class="col-sm-2">{% trans "Status" %}</th>
<th span="col" class="col-sm-5">{% trans "Details" %}</th>
<th span="col" class="col-sm-2 text-center">{% trans "Actions" %}</th>
</tr>
</thead>
<tbody class="searchable">
{% for compute in computes %}
<tr>
<td>
<tr class="d-flex">
<td class="col-sm-3">
{{ compute.name }}
</td>
<td>
<td class="col-sm-2">
{% if compute.status is True %}{% trans "Connected" %}{% else %}{% trans "Not Connected" %}{% endif %}
</td>
<td>
<td class="col-sm-5">
{{ compute.details|default:"" }}
</td>
<td>
<td class="col-sm-2">
<div class="float-right btn-group">
{% if compute.status is True %}
<a class="btn btn-success" title="{%trans "Overview" %}" href="{% url 'overview' compute.id %}">{% icon 'eye' %}</a>

View file

@ -160,6 +160,11 @@
SpiceHtml5.sendCtrlAltFN(sc, f);
return false;
}
function sendCtrlAltDel() {
SpiceHtml5.sendCtrlAltDel(sc);
return false;
}
/* SPICE port event listeners
window.addEventListener('spice-port-data', function(event) {
// Here we convert data to text, but really we can obtain binary data also

View file

@ -186,6 +186,11 @@
return false;
}
function sendCtrlAltDel() {
SpiceHtml5.sendCtrlAltDel(sc);
return false;
}
/* SPICE port event listeners
window.addEventListener('spice-port-data', function(event) {
// Here we convert data to text, but really we can obtain binary data also
@ -198,7 +203,7 @@
});
*/
document.getElementById("fullscreen_button").addEventListener('click', fullscreen);
document.getElementById('ctrlaltdel').addEventListener('click', function () { sendCtrlAltDel(sc); });
document.getElementById('ctrlaltdel').addEventListener('click', function () { sendCtrlAltDel(); });
document.getElementById('ctrlaltf1').addEventListener('click', function () { sendctrlaltfn(0) });
document.getElementById('ctrlaltf2').addEventListener('click', function () { sendctrlaltfn(1) });
document.getElementById('ctrlaltf3').addEventListener('click', function () { sendctrlaltfn(2) });

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View file

@ -12,6 +12,7 @@
<!-- Tab panes -->
<div class="tab-content">
<div role="tabpanel" class="tab-pane tab-pane-bordered active" id="destroy">
<p>{% trans 'This action starts remove instance process' %}</p>
{% if request.user.is_superuser or userinstance.is_delete %}
{% if instance.status == 3 %}
<a class="btn btn-lg btn-success disabled float-right">{% trans "Destroy" %}</a>

View file

@ -353,6 +353,7 @@
<input name="set_link_state" value="{{ network.state }}" hidden/>
<input type="checkbox" {% if network.state == 'up' %} checked{% endif %} onclick='submit();' />
<strong>{% trans 'active' %}</strong>
<small>{{ network.type }}</small>
</form>
</td>
<th class="d-none d-table-cell d-sm-table-cell">{% trans 'MAC' %}</th>
@ -390,10 +391,10 @@
<input class="form-control" type="text" value="{{ network.nic }}" readonly/>
<select class="form-control" name="net-source-{{ forloop.counter0 }}">
{% for c_net in networks_host %}
<option value="net:{{ c_net }}" {% if c_net == network.nic %} selected {% endif %}>{% trans 'Network' %} {{ c_net }}</option>
<option value="net:{{ c_net }}" {% if c_net == network.nic %} selected {% endif %}>{% trans 'Network' %} {{ c_net }}</option>
{% endfor %}
{% for c_iface in interfaces_host %}
<option value="iface:{{ c_iface }}" {% if c_iface == network.nic %} selected {% endif %}>{% trans 'Interface' %} {{ c_iface }}</option>
<option value="iface:{{ c_iface }}" {% if c_iface == network.nic %} selected {% endif %}>{% trans 'Interface' %} {{ c_iface }}</option>
{% endfor %}
</select>
</div>
@ -405,7 +406,7 @@
<select class="form-control" name="net-nwfilter-{{ forloop.counter0 }}">
<option value="">{% trans "None" %}</option>
{% for c_filters in nwfilters_host %}
<option value="{{ c_filters }}" {% if c_filters == network.filterref %} selected {% endif %}>{{ c_filters }}</option>
<option value="{{ c_filters }}" {% if c_filters == network.filterref %} selected {% endif %}>{{ c_filters }}</option>
{% endfor %}
</select>
</div>
@ -462,7 +463,11 @@
</td>
</tr>
<tr>
<td class="bg-primary" colspan="9"></td>
<td class="py-1 bg-primary text-white" colspan="9">
{% if network.type == 'direct' %}
<small>{% trans 'In most configurations, macvtap does not work for host to guest network communication' %}</small>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>

View file

@ -965,8 +965,8 @@ def set_qos(request, pk):
else:
messages.success(
request,
_(f"{qos_dir.capitalize()} QoS is set. Network XML is changed.") +
_("Stop and start network to activate new config"),
_(f"{qos_dir.capitalize()} QoS is set. Network XML is changed. \
Stop and start network to activate new config.")
)
return redirect(request.META.get('HTTP_REFERER') + '#network')
@ -984,8 +984,8 @@ def unset_qos(request, pk):
else:
messages.success(
request,
_(f"{qos_dir.capitalize()} QoS is deleted. Network XML is changed. ") +
_("Stop and start network to activate new config."),
_(f"{qos_dir.capitalize()} QoS is deleted. Network XML is changed. \
Stop and start network to activate new config.")
)
return redirect(request.META.get('HTTP_REFERER') + '#network')
@ -1409,7 +1409,7 @@ def create_instance(request, compute_id, arch, machine):
console_pass=data["console_pass"],
mac=data['mac'],
qemu_ga=data['qemu_ga'])
create_instance = Instance(compute_id=compute_id, name=data['name'])
create_instance = Instance(compute_id=compute_id, name=data['name'], uuid=uuid)
create_instance.save()
msg = _("Instance is created")
messages.success(request, msg)

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

Binary file not shown.

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -46,7 +46,7 @@ class AddNetPool(forms.Form):
def clean_bridge_name(self):
bridge_name = self.cleaned_data['bridge_name']
if self.cleaned_data['forward'] == 'bridge':
if self.cleaned_data['forward'] in ['bridge', 'macvtap']:
have_symbol = re.match('^[a-zA-Z0-9\.\_\:\-]+$', bridge_name)
if not have_symbol:
raise forms.ValidationError(_('The pool bridge name must not contain any special characters'))

View file

@ -28,6 +28,7 @@
<option value="route">{% trans "ROUTE" %}</option>
<option value="none">{% trans "ISOLATE" %}</option>
<option value="bridge">{% trans "BRIDGE" %}</option>
<option value="macvtap">{% trans "MACVTAP" %}</option>
</select>
</div>
</div>
@ -55,25 +56,25 @@
<input type="checkbox" id="enable_ipv6" name="enable_ipv6" value="false">
</div>
</div>
<div class="form-group row bridge_name_form_group_dhcp ipv6_group">
<div class="form-group row ipv6_group">
<label class="col-sm-4 col-form-label">{% trans "IPv6 Subnet pool" %}</label>
<div class="col-sm-6">
<input type="text" class="form-control" name="subnet6" value="" placeholder="fd00:dead:baba:1::/64" required pattern="[0-9\/\.]+">
</div>
</div>
<div class="form-group row bridge_name_form_group_dhcp ipv6_group">
<div class="form-group row ipv6_group">
<label class="col-sm-4 col-form-label">{% trans "DHCPv6" %}</label>
<div class="col-sm-6">
<input type="checkbox" name="dhcp6" value="true">
</div>
</div>
<div class="form-group row bridge_name_form_group">
<label class="col-sm-4 col-form-label">{% trans "Bridge Name" %}</label>
<label class="col-sm-4 col-form-label" id="bridge_label">{% trans "Bridge Name" %}</label>
<div class="col-sm-6">
<input type="text" class="form-control" name="bridge_name" placeholder="br0" pattern="[a-z0-9\-_:]+">
<input type="text" class="form-control" name="bridge_name" id="bridge_name" placeholder="br0" pattern="[a-z0-9\-_:]+">
</div>
</div>
<div class="form-group row bridge_name_form_group">
<div class="form-group row bridge_name_form_group openvswitch">
<label class="col-sm-4 col-form-label">{% trans "Open vSwitch" %}</label>
<div class="col-sm-6">
<input type="checkbox" name="openvswitch" value="true">
@ -89,3 +90,4 @@
</div> <!-- /.modal-dialog -->
</div> <!-- /.modal -->
{% endif %}

View file

@ -37,7 +37,6 @@
<!-- /.row -->
{% include 'errors_block.html' %}
{% include 'messages_block.html' %}
<div class="row">
<dl class="ml-3 row">

View file

@ -79,6 +79,12 @@
if ($(this).val() == 'bridge') {
$('.bridge_name_form_group').show();
$('.bridge_name_form_group_dhcp').hide();
} else if ($(this).val() == 'macvtap') {
$('#bridge_label').text("Dev Name");
$('#bridge_name').attr("placeholder", "eth0");
$('.bridge_name_form_group').show();
$('.bridge_name_form_group_dhcp').hide();
$('.openvswitch').hide();
} else {
$('.bridge_name_form_group').hide();
$('.bridge_name_form_group_dhcp').show();

View file

@ -42,8 +42,8 @@ def networks(request, compute_id):
if data['name'] in networks:
msg = _("Network pool name already in use")
error_messages.append(msg)
if data['forward'] == 'bridge' and data['bridge_name'] == '':
error_messages.append(_('Please enter bridge name'))
if data['forward'] in ['bridge', 'macvtap'] and data['bridge_name'] == '':
error_messages.append(_('Please enter bridge/dev name'))
if data['subnet']:
ipv4 = True
gateway4, netmask4, dhcp4 = network_size(data['subnet'], data['dhcp4'])

View file

@ -841,7 +841,7 @@ class wvmConnect(object):
def get_info(doc):
mem = util.get_xpath(doc, "/domain/currentMemory")
mem = int(mem) / 1024
mem = int(mem) // 1024
if raw_mem_size:
mem = int(mem) * (1024 * 1024)
cur_vcpu = util.get_xpath(doc, "/domain/vcpu/@current")
@ -875,7 +875,7 @@ class wvmConnect(object):
def get_info(ctx):
mem = util.get_xpath(ctx, "/domain/currentMemory")
mem = int(mem) / 1024
mem = int(mem) // 1024
cur_vcpu = util.get_xpath(ctx, "/domain/vcpu/@current")
if cur_vcpu:
vcpu = cur_vcpu

View file

@ -32,7 +32,7 @@ class wvmInstances(wvmConnect):
def get_instance_memory(self, name):
inst = self.get_instance(name)
mem = util.get_xml_path(inst.XMLDesc(0), "/domain/currentMemory")
return int(mem) / 1024
return int(mem) // 1024
def get_instance_vcpu(self, name):
inst = self.get_instance(name)
@ -242,11 +242,11 @@ class wvmInstance(wvmConnect):
def get_memory(self):
mem = util.get_xml_path(self._XMLDesc(0), "/domain/memory")
return int(mem) / 1024
return int(mem) // 1024
def get_cur_memory(self):
mem = util.get_xml_path(self._XMLDesc(0), "/domain/currentMemory")
return int(mem) / 1024
return int(mem) // 1024
def get_title(self):
title = util.get_xml_path(self._XMLDesc(0), "/domain/title")
@ -345,6 +345,7 @@ class wvmInstance(wvmConnect):
result = []
inbound = outbound = []
for net in ctx.xpath('/domain/devices/interface'):
interface_type = net.xpath('@type')[0]
mac_inst = net.xpath('mac/@address')[0]
nic_inst = net.xpath('source/@network|source/@bridge|source/@dev')[0]
target_inst = '' if not net.xpath('target/@dev') else net.xpath('target/@dev')[0]
@ -369,6 +370,7 @@ class wvmInstance(wvmConnect):
except libvirtError:
ipv4, ipv6 = None, None
result.append({
'type': interface_type,
'mac': mac_inst,
'nic': nic_inst,
'target': target_inst,
@ -993,7 +995,7 @@ class wvmInstance(wvmConnect):
except SyntaxError:
# Little fix for old version ElementTree
graphic = root.find("devices/graphics")
if keymap:
if keymap != 'auto':
graphic.set('keymap', keymap)
else:
try:
@ -1289,24 +1291,39 @@ class wvmInstance(wvmConnect):
bridge_name = iface.name()
else:
net = self.get_network(source)
bridge_name = net.bridgeName()
try:
bridge_name = net.bridgeName()
except libvirtError:
bridge_name = None
return bridge_name
def add_network(self, mac_address, source, source_type='net', model='virtio', nwfilter=None):
bridge_name = self.get_bridge_name(source, source_type)
forward_mode = ''
if source_type != 'iface':
forward_mode = self.get_network_forward(source)
forward_mode = self.get_network_forward(source)
if forward_mode in ['nat', 'isolated', 'routed']:
interface_type = 'network'
elif forward_mode == '':
interface_type = 'direct'
else:
interface_type = 'bridge'
if self.get_bridge_name(source, source_type) is None:
interface_type = 'network'
else:
interface_type = 'bridge'
xml_iface = f"""
<interface type='{interface_type}'>
<mac address='{mac_address}'/>"""
if interface_type == 'network':
xml_iface += f"""<source network='{source}'/>"""
elif interface_type == 'direct':
if source_type == 'net':
xml_iface += f"""<source network='{source}' mode='bridge'/>"""
else:
xml_iface += f"""<source dev='{source}' mode='bridge'/>"""
else:
bridge_name = self.get_bridge_name(source, source_type)
xml_iface += f"""<source bridge='{bridge_name}'/>"""
xml_iface += f"""<model type='{model}'/>"""
if nwfilter:
@ -1342,49 +1359,38 @@ class wvmInstance(wvmConnect):
net_source_type = network_data.get('net-source-' + str(num) + '-type')
net_filter = network_data.get('net-nwfilter-' + str(num))
net_model = network_data.get('net-model-' + str(num))
bridge_name = self.get_bridge_name(net_source, net_source_type)
source = interface.find('source')
if interface.get('type') == 'bridge':
source = interface.find('mac')
source.set('address', net_mac)
source = interface.find('source')
bridge_name = self.get_bridge_name(net_source, net_source_type)
source.set('bridge', bridge_name)
source = interface.find('model')
if net_model != 'default':
source.attrib['type'] = net_model
elif interface.get('type') in ['network', 'direct']:
if net_source_type == 'net':
source.set('network', net_source)
elif net_source_type == 'iface':
source.set('dev', net_source)
else:
interface.remove(source)
raise libvirtError(f"Unknown network type: {net_source_type}")
else:
raise libvirtError(f"Unknown network type: {interface.get('type')}")
source = interface.find('filterref')
if net_filter:
if source is not None: source.set('filter', net_filter)
else:
element = ElementTree.Element("filterref")
element.attrib['filter'] = net_filter
interface.append(element)
else:
if source is not None: interface.remove(source)
elif interface.get('type') == 'network':
source = interface.find('mac')
source.set('address', net_mac)
source = interface.find('source')
source.set('network', net_source)
source = interface.find('model')
if net_model != 'default':
source.attrib['type'] = net_model
else:
interface.remove(source)
source = interface.find('model')
if net_model != 'default':
source.attrib['type'] = net_model
source = interface.find('mac')
source.set('address', net_mac)
source = interface.find('filterref')
if net_filter:
if source is not None: source.set('filter', net_filter)
else:
interface.remove(source)
source = interface.find('filterref')
if net_filter:
if source is not None: source.set('filter', net_filter)
else:
element = ElementTree.Element("filterref")
element.attrib['filter'] = net_filter
interface.append(element)
else:
if source is not None: interface.remove(source)
element = ElementTree.Element("filterref")
element.attrib['filter'] = net_filter
interface.append(element)
else:
if source is not None: interface.remove(source)
new_xml = ElementTree.tostring(tree).decode()
self._defineXML(new_xml)

View file

@ -1,4 +1,5 @@
from lxml import etree
from libvirt import libvirtError
from libvirt import VIR_NETWORK_SECTION_IP_DHCP_HOST
from libvirt import VIR_NETWORK_UPDATE_COMMAND_ADD_LAST, VIR_NETWORK_UPDATE_COMMAND_DELETE, VIR_NETWORK_UPDATE_COMMAND_MODIFY
from libvirt import VIR_NETWORK_UPDATE_AFFECT_LIVE, VIR_NETWORK_UPDATE_AFFECT_CONFIG
@ -32,7 +33,11 @@ class wvmNetworks(wvmConnect):
for network in get_networks:
net = self.get_network(network)
net_status = net.isActive()
net_bridge = net.bridgeName()
try:
net_bridge = net.bridgeName()
except libvirtError:
net_bridge = util.get_xml_path(net.XMLDesc(0), "/network/forward/interface/@dev")
net_forward = util.get_xml_path(net.XMLDesc(0), "/network/forward/@mode")
networks.append({'name': network, 'status': net_status,
'device': net_bridge, 'forward': net_forward})
@ -50,15 +55,20 @@ class wvmNetworks(wvmConnect):
<name>{name}</name>"""
if forward in ['nat', 'route', 'bridge']:
xml += f"""<forward mode='{forward}'/>"""
xml += """<bridge """
if forward in ['nat', 'route', 'none']:
xml += """stp='on' delay='0'"""
if forward == 'bridge':
xml += f"""name='{bridge}'"""
xml += """/>"""
if openvswitch is True:
xml += """<virtualport type='openvswitch'/>"""
if forward != 'bridge':
if forward == 'macvtap':
xml += f"""<forward mode='bridge'>
<interface dev='{bridge}'/>
</forward>"""
else:
xml += """<bridge """
if forward in ['nat', 'route', 'none']:
xml += """stp='on' delay='0'"""
if forward == 'bridge':
xml += f"""name='{bridge}'"""
xml += """/>"""
if openvswitch is True:
xml += """<virtualport type='openvswitch'/>"""
if forward not in ['bridge', 'macvtap']:
if ipv4:
xml += f"""<ip address='{gateway}' netmask='{mask}'>"""
if dhcp4:
@ -114,7 +124,7 @@ class wvmNetwork(wvmConnect):
try:
return self.net.bridgeName()
except:
return None
return util.get_xml_path(self._XMLDesc(0), "/network/forward/interface/@dev")
def start(self):
self.net.create()

View file

@ -19,8 +19,11 @@ urlpatterns = [
]
if settings.DEBUG:
import debug_toolbar
try:
import debug_toolbar
urlpatterns += [
path('__debug__/', include(debug_toolbar.urls)),
]
urlpatterns += [
path('__debug__/', include(debug_toolbar.urls)),
]
except:
pass