Browse Source

Implement ip leases management

devel
bashmak 8 years ago
parent
commit
dcf4380d5f
  1. 6
      abonapp/forms.py
  2. 11
      abonapp/models.py
  3. 12
      abonapp/templates/abonapp/editAbon.html
  4. 25
      abonapp/templates/abonapp/modal_add_lease.html
  5. 26
      abonapp/templates/abonapp/modal_current_networks.html
  6. 4
      abonapp/urls.py
  7. 53
      abonapp/views.py
  8. 15
      agent/commands/dhcp.py
  9. 3
      devapp/forms.py
  10. 6
      devapp/views.py
  11. 6
      djing/lib/__init__.py
  12. 2
      ip_pool/fields.py
  13. 32
      ip_pool/forms.py
  14. 119
      ip_pool/models.py
  15. 2
      ip_pool/templates/ip_pool/ip_leases_list.html
  16. 8
      ip_pool/templates/ip_pool/net_edit.html
  17. 33
      ip_pool/templates/ip_pool/network_groups_available.html
  18. 23
      ip_pool/templates/ip_pool/network_list.html
  19. 19
      ip_pool/templates/ip_pool/networkmodel_confirm_delete.html
  20. 4
      ip_pool/urls.py
  21. 37
      ip_pool/views.py

6
abonapp/forms.py

@ -41,7 +41,7 @@ class AbonForm(forms.ModelForm):
abon_group_queryset = None
if abon_group_queryset is not None:
self.fields['street'].queryset = abon_group_queryset
#if instance is not None and instance.is_dynamic_ip:
# if instance is not None and instance.is_dynamic_ip:
# self.fields['ip_address'].widget.attrs['readonly'] = True
username = forms.CharField(max_length=127, required=False, initial=generate_random_username,
@ -139,7 +139,7 @@ class ExportUsersForm(forms.Form):
FIELDS_CHOICES = (
('username', _('profile username')),
('fio', _('fio')),
#('ip_address', _('Ip Address')),
# ('ip_address', _('Ip Address')),
('description', _('Comment')),
('street__name', _('Street')),
('house', _('House')),
@ -160,7 +160,7 @@ class ExportUsersForm(forms.Form):
class MarkersForm(forms.ModelForm):
class Meta:
model = models.Abon
fields = ('markers',)
fields = 'markers',
def save(self, commit=True):
instance = super(MarkersForm, self).save(commit=False)

11
abonapp/models.py

@ -138,8 +138,6 @@ class Abon(BaseAccount):
('can_add_ballance', _('fill account')),
('can_ping', _('Can ping'))
)
# TODO: Fix when duplicates already in database
# unique_together = ('device', 'dev_port')
verbose_name = _('Abon')
verbose_name_plural = _('Abons')
ordering = ('fio',)
@ -251,6 +249,15 @@ class Abon(BaseAccount):
def get_absolute_url(self):
return resolve_url('abonapp:abon_home', self.group.id, self.username)
def add_lease(self, ip: str):
existed_client_ips = tuple(l.ip for l in self.ip_addresses.all())
if ip not in existed_client_ips:
lease = IpLeaseModel.objects.create_from_ip(ip=ip)
if lease is None:
return 'Subnet not found'
self.ip_addresses.add(lease)
#self.save()
class PassportInfo(models.Model):
series = models.CharField(_('Pasport serial'), max_length=4, validators=(validators.integer_validator,))

12
abonapp/templates/abonapp/editAbon.html

@ -220,10 +220,20 @@
{% endfor %}
</ul>
<div class="panel-footer">
<a href="#" class="btn btn-success btn-sm" {{ abon.is_dynamic_ip|yesno:'disabled,' }}>
<div class="btn-group btn-group-sm">
{% if abon.is_dynamic_ip %}
<a href="#" class="btn btn-success" disabled>
{% else %}
<a href="{% url 'abonapp:lease_add' group.pk abon.username %}" class="btn btn-success btn-modal">
{% endif %}
<span class="glyphicon glyphicon-plus"></span>
<span class="hidden-xs">{% trans 'Add' %}</span>
</a>
<a href="{% url 'abonapp:active_nets' group.pk %}" class="btn btn-default btn-modal">
<span class="glyphicon glyphicon-globe"></span>
<span class="hidden-sm hidden-xs">{% trans 'Active networks' %}</span>
</a>
</div>
</div>
</div>

25
abonapp/templates/abonapp/modal_add_lease.html

@ -0,0 +1,25 @@
{% extends request.is_ajax|yesno:'nullcont.htm,abonapp/ext.htm' %}
{% load i18n %}
{% load bootstrap3 %}
{% block content %}
<form role="form" action="{% url 'abonapp:lease_add' group.id uname %}" method="post"> {% csrf_token %}
<div class="modal-header primary">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title"><span class="glyphicon glyphicon-compressed"></span>{% trans 'Add ip lease' %}</h4>
</div>
<div class="modal-body">
{% bootstrap_form form %}
<div class="btn-group">
<button type="submit" class="btn btn-success">
<span class="glyphicon glyphicon-plus"></span> {% trans 'Add' %}
</button>
<button type="reset" class="btn btn-default">
<span class="glyphicon glyphicon-remove-circle"></span> {% trans 'Reset' %}
</button>
</div>
</div>
</form>
{% endblock %}

26
abonapp/templates/abonapp/modal_current_networks.html

@ -0,0 +1,26 @@
{% load i18n %}
<div class="modal-header primary">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title"><span class="glyphicon glyphicon-globe"></span>{% trans 'Available networks' %}</h4>
</div>
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>{% trans 'Network' %}</th>
<th>{% trans 'Scope' %}</th>
</tr>
</thead>
<tbody>
{% for net_item in networks %}
<tr>
<td><a href="#" class="btn btn-link">{{ net_item }}</a></td>
<td>{{ net_item.get_scope }}</td>
</tr>
{% empty %}
<tr>
<td colspan="2">{% trans 'Available networks not found' %}</td>
</tr>
{% endfor %}
</tbody>
</table>

4
abonapp/urls.py

@ -30,7 +30,8 @@ subscriber_patterns = [
url(r'^session/(?P<lease_id>\d+)/start$', views.user_session_toggle, {'action': 'start'}, name='user_session_start'),
url(r'^periodic_pay$', views.add_edit_periodic_pay, name='add_periodic_pay'),
url(r'^periodic_pay(?P<periodic_pay_id>\d+)/$', views.add_edit_periodic_pay, name='add_periodic_pay'),
url(r'^periodic_pay(?P<periodic_pay_id>\d+)/del/$', views.del_periodic_pay, name='del_periodic_pay')
url(r'^periodic_pay(?P<periodic_pay_id>\d+)/del/$', views.del_periodic_pay, name='del_periodic_pay'),
url(r'^lease/add/$', views.lease_add, name='lease_add')
]
group_patterns = [
@ -42,6 +43,7 @@ group_patterns = [
url(r'^street/add$', views.street_add, name='street_add'),
url(r'^street/edit', views.street_edit, name='street_edit'),
url(r'^street/(?P<sid>\d+)/delete$', views.street_del, name='street_del'),
url(r'^active_networks/$', views.active_nets, name='active_nets'),
url(r'^(?P<uname>\w{1,127})/', include(subscriber_patterns))
]

53
abonapp/views.py

@ -1,4 +1,5 @@
from typing import Dict, Optional
from datetime import datetime, date, timedelta
from django.contrib.gis.shortcuts import render_to_text
from django.core.exceptions import PermissionDenied, ValidationError
from django.db import IntegrityError, ProgrammingError, transaction
@ -22,11 +23,12 @@ from agent import NasFailedResult, Transmitter, NasNetworkError
from . import forms
from . import models
from devapp.models import Device, Port as DevPort
from datetime import datetime, date, timedelta
from taskapp.models import Task
from dialing_app.models import AsteriskCDR
from statistics.models import getModel
from group_app.models import Group
from ip_pool.models import IpLeaseModel, NetworkModel
from ip_pool.forms import LeaseForm
from guardian.shortcuts import get_objects_for_user, assign_perm
from guardian.decorators import permission_required_or_403 as permission_required
from djing import ping
@ -545,7 +547,7 @@ def chgroup_tariff(request, gid):
if request.method == 'POST':
tr = request.POST.getlist('tr')
grp.tariff_set.clear()
grp.tariff_set.add(*(int(d) for d in tr))
grp.tariff_set.add(*tr)
grp.save()
messages.success(request, _('Successfully saved'))
return redirect('abonapp:ch_group_tariff', gid)
@ -836,6 +838,15 @@ def street_del(request, gid, sid):
return redirect('abonapp:people_list', gid)
@login_required
@permission_required('group_app.can_view_group', (Group, 'pk', 'gid'))
def active_nets(request, gid):
nets = NetworkModel.objects.filter(groups__id=gid)
return render(request, 'abonapp/modal_current_networks.html', {
'networks': nets
})
@login_required
@permission_required('abonapp.can_view_additionaltelephones')
@permission_required('group_app.can_view_group', (Group, 'pk', 'gid'))
@ -1071,6 +1082,44 @@ def user_session_toggle(request, gid, uname, lease_id, action=None):
return redirect('abonapp:abon_home', gid, uname)
@login_required
@lib.decorators.only_admins
@permission_required('change_abon')
def lease_add(request, gid, uname):
group = get_object_or_404(Group, pk=gid)
if request.method == 'POST':
frm = LeaseForm(request.POST)
if frm.is_valid():
try:
abon = get_object_or_404(models.Abon, username=uname)
cleaned = frm.clean()
ip = cleaned.get('ip_addr')
is_dynamic = cleaned.get('is_dynamic')
network_id = cleaned.get('possible_networks') # str(int)
network = get_object_or_404(NetworkModel, pk=network_id)
lease = IpLeaseModel.objects.create_from_ip(ip, net=network, is_dynamic=is_dynamic)
abon.ip_addresses.add(lease)
messages.success(request, _('Ip lease has been created'))
return redirect('abonapp:abon_home', gid, uname)
except lib.DuplicateEntry as e:
messages.error(request, e)
else:
messages.error(request, _('Check form errors'))
else:
first_network = NetworkModel.objects.filter(groups=group).first()
if first_network is not None:
free_ip = IpLeaseModel.objects.get_free_ip(first_network)
initial = {'ip_addr': free_ip}
else:
initial = None
frm = LeaseForm(initial=initial)
return render(request, 'abonapp/modal_add_lease.html', {
'form': frm,
'group': group,
'uname': uname
})
# API's
@login_required
@lib.decorators.only_admins

15
agent/commands/dhcp.py

@ -18,19 +18,14 @@ def dhcp_commit(client_ip: str, client_mac: str, switch_mac: str, switch_port: i
abon = Abon.objects.get(device=dev)
if not abon.is_dynamic_ip:
return 'User settings is not dynamic'
existed_client_ips = tuple(l.ip for l in abon.ip_addresses.all())
if client_ip not in existed_client_ips:
lease = IpLeaseModel.objects.create_from_ip(
ip=client_ip,
)
if lease is None:
return 'Subnet not found'
abon.ip_addresses.add(lease)
abon.save()
add_lease_result = abon.add_lease(client_ip)
if add_lease_result is None:
if abon.is_access():
abon.sync_with_nas(created=False)
else:
print('D:', 'User %s is not access to service' % abon.username)
return 'User %s is not access to service' % abon.username
else:
return add_lease_result
except Abon.DoesNotExist:
return "User with device with mac '%s' does not exist" % switch_mac
except Device.DoesNotExist:

3
devapp/forms.py

@ -3,6 +3,7 @@ from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
from django.db import IntegrityError
from djing.lib import DuplicateEntry
from djing.lib.tln.tln import ValidationError as TlnValidationError
from . import models
from djing import MAC_ADDR_REGEX, IP_ADDR_REGEX
@ -82,6 +83,6 @@ class PortForm(forms.ModelForm):
super(PortForm, self).save(commit)
except IntegrityError as e:
if "Duplicate entry" in str(e):
raise models.DeviceDBException(_('Port number on device must be unique'))
raise DuplicateEntry(_('Port number on device must be unique'))
else:
raise models.DeviceDBException(e)

6
devapp/views.py

@ -14,7 +14,7 @@ from django.views.generic import DetailView, DeleteView, UpdateView, CreateView
from devapp.base_intr import DeviceImplementationError
from djing.lib.decorators import only_admins, hash_auth_view
from djing.lib import safe_int, ProcessLocked
from djing.lib import safe_int, ProcessLocked, DuplicateEntry
from abonapp.models import Abon
from djing.lib.tln import ZteOltConsoleError, OnuZteRegisterError, ZteOltLoginFailed
from group_app.models import Group
@ -386,7 +386,7 @@ def edit_single_port(request, group_id, device_id, port_id):
}, request=request)
except Port.DoesNotExist:
messages.error(request, _('Port does not exist'))
except DeviceDBException as e:
except (DeviceDBException, DuplicateEntry) as e:
messages.error(request, e)
return redirect('devapp:manage_ports', group_id, device_id)
@ -416,7 +416,7 @@ def add_single_port(request, group_id, device_id):
}, request=request)
except Device.DoesNotExist:
messages.error(request, _('Device does not exist'))
except DeviceDBException as e:
except (DeviceDBException, DuplicateEntry) as e:
messages.error(request, e)
return redirect('devapp:manage_ports', group_id, device_id)

6
djing/lib/__init__.py

@ -142,3 +142,9 @@ def process_lock(fn):
if s is not None:
s.close()
return wrapped
#
# Raises when IntegrityError in db
#
class DuplicateEntry(Exception):
pass

2
ip_pool/fields.py

@ -56,7 +56,7 @@ class GenericIpAddressWithPrefix(models.GenericIPAddressField):
value = value.strip()
if ':' in value:
ip = ip_network(value)
return ip.compressed
return ip.with_prefixlen
return value
def get_db_prep_value(self, value, connection, prepared=False):

32
ip_pool/forms.py

@ -1,7 +1,8 @@
from ipaddress import ip_network
from ipaddress import ip_network, ip_address
from django import forms
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext as _
from ip_pool import models
@ -21,3 +22,32 @@ class NetworkForm(forms.ModelForm):
class Meta:
model = models.NetworkModel
fields = '__all__'
class LeaseForm(forms.Form):
def __init__(self, data=None, *args, **kwargs):
super(LeaseForm, self).__init__(data=data, *args, **kwargs)
nets = models.NetworkModel.objects.defer('groups')
if nets.exists():
self.fields['possible_networks'].choices = ((net.pk, str(net.get_network())) for net in nets.iterator())
def clean_ip_addr(self):
ip_addr = self.data.get('ip_addr')
if ip_addr is None:
return
ip_addr = ip_address(ip_addr)
net_id = self.data.get('possible_networks')
if net_id is None:
return ip_addr.compressed
net = models.NetworkModel.objects.get(pk=net_id)
if ip_addr not in net.get_network():
raise ValidationError(_('Ip that you typed is not in subnet that you have selected'))
if ip_addr < ip_address(net.ip_start):
raise ValidationError(_('Ip that you have passed is less than allowed network range'))
if ip_addr > ip_address(net.ip_end):
raise ValidationError(_('Ip that you have passed is greater than allowed network range'))
return ip_addr.compressed
ip_addr = forms.GenericIPAddressField(label=_('Ip address'))
is_dynamic = forms.BooleanField(label=_('Is dynamic'), required=False)
possible_networks = forms.ChoiceField(label=_('Possible networks'))

119
ip_pool/models.py

@ -1,15 +1,18 @@
from datetime import timedelta
from ipaddress import ip_network, ip_address
from typing import AnyStr
from django.conf import settings
from django.db.utils import IntegrityError
from django.shortcuts import resolve_url
from django.core.exceptions import ValidationError, ImproperlyConfigured
from django.db import models
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
#from djing.fields import MACAddressField
from djing.lib import DuplicateEntry
from ip_pool.fields import GenericIpAddressWithPrefix
from group_app.models import Group
class NetworkModel(models.Model):
@ -29,11 +32,19 @@ class NetworkModel(models.Model):
)
kind = models.CharField(_('Kind of network'), max_length=6, choices=NETWORK_KINDS, default='guest')
description = models.CharField(_('Description'), max_length=64)
groups = models.ManyToManyField(Group, verbose_name=_('Groups'))
# Usable ip range
ip_start = models.GenericIPAddressField(_('Start work ip range'))
ip_end = models.GenericIPAddressField(_('End work ip range'))
def __str__(self):
return "%s: %s" % (self.description, self.network)
netw = self.get_network()
return "%s: %s" % (self.description, netw.with_prefixlen)
def get_network(self):
if self.network is None:
return
if self._netw_cache is None:
self._netw_cache = ip_network(self.network)
return self._netw_cache
@ -41,35 +52,104 @@ class NetworkModel(models.Model):
def get_absolute_url(self):
return resolve_url('ip_pool:net_edit', self.pk)
def clean(self):
errs = {}
if self.network is None:
errs['network'] = ValidationError(_('Network is invalid'), code='invalid')
raise ValidationError(errs)
net = self.get_network()
if self.ip_start is None:
errs['ip_start'] = ValidationError(_('Ip start is invalid'), code='invalid')
raise ValidationError(errs)
start_ip = ip_address(self.ip_start)
if start_ip not in net:
errs['ip_start'] = ValidationError(_('Start ip must be in subnet of specified network'), code='invalid')
if self.ip_end is None:
errs['ip_end'] = ValidationError(_('Ip end is invalid'), code='invalid')
raise ValidationError(errs)
end_ip = ip_address(self.ip_end)
if end_ip not in net:
errs['ip_end'] = ValidationError(_('End ip must be in subnet of specified network'), code='invalid')
if errs:
raise ValidationError(errs)
other_nets = NetworkModel.objects.exclude(pk=self.pk).only('network').order_by('network')
if not other_nets.exists():
return
for onet in other_nets.iterator():
onet_netw = onet.get_network()
if net.overlaps(onet_netw):
errs['network'] = ValidationError(_('Network is overlaps with %(other_network)s'), params={
'other_network': str(onet_netw)
})
raise ValidationError(errs)
def get_scope(self) -> AnyStr:
net = self.get_network()
if net.is_global:
return _('Global')
elif net.is_link_local:
return _('Link local')
elif net.is_loopback:
return _('Loopback')
elif net.is_multicast:
return _('Multicast')
elif net.is_private:
return _('Private')
elif net.is_reserved:
return _('Reserved')
elif net.is_site_local:
return _('Site local')
elif net.is_unspecified:
return _('Unspecified')
return "I don't know"
class Meta:
db_table = 'ip_pool_network'
verbose_name = _('Network')
verbose_name_plural = _('Networks')
ordering = ('description',)
ordering = ('network',)
class IpLeaseManager(models.Manager):
def get_free_ip(self, network: NetworkModel):
netw = ip_network(network)
employed_ip_queryset = self.filter(network=network)
free_ip = next(ip_address(net) for ip, net in zip(
employed_ip_queryset, netw
) if ip != net)
return free_ip
def create_from_ip(self, ip: str, cidr_subnet: int):
# FIXME: get subnet
raise NotImplementedError
net = ip_network((ip, cidr_subnet), strict=False)
netw_instance = NetworkModel.objects.filter(network=str(net)).first()
if netw_instance is not None:
netw = network.get_network()
work_range_start_ip = ip_address(network.ip_start)
work_range_end_ip = ip_address(network.ip_end)
employed_ip_queryset = self.filter(network=network, is_dynamic=False).order_by('ip').only('ip')
if employed_ip_queryset.exists():
used_ip_gen = employed_ip_queryset.iterator()
for net_ip in netw.hosts():
if net_ip < work_range_start_ip:
continue
elif net_ip > work_range_end_ip:
break
used_ip = next(used_ip_gen, None)
if used_ip is None:
return net_ip
ip = ip_address(used_ip.ip)
if net_ip < ip:
return net_ip
else:
for net in netw.hosts():
if work_range_start_ip <= net <= work_range_end_ip:
return net
def create_from_ip(self, ip: str, net: NetworkModel, is_dynamic=True):
# ip = ip_address(ip)
try:
return self.create(
ip=ip,
network=netw_instance,
is_dynamic=True,
network=net,
is_dynamic=is_dynamic,
is_active=True
)
except IntegrityError as e:
if 'Duplicate entry' in str(e):
raise DuplicateEntry(_('Ip has already taken'))
raise e
def expired(self):
lease_live_time = getattr(settings, 'LEASE_LIVE_TIME')
@ -104,9 +184,8 @@ class IpLeaseModel(models.Model):
def clean(self):
ip = ip_address(self.ip)
network = self.network.get_network()
if ip not in network:
raise ValidationError(_('Ip address %(ip)s not in %(net)s network') % {
raise ValidationError(_('Ip address %(ip)s not in %(net)s network'), params={
'ip': ip,
'net': network
}, code='invalid')

2
ip_pool/templates/ip_pool/ip_leases_list.html

@ -28,7 +28,7 @@
<tr>
<td>{{ ip.ip }}</td>
<td>{{ ip.lease_time|date:'j:n H:i:s' }}</td>
<td>{{ ip.network }}</td>
<td>{{ ip.get_network }}</td>
<td><input type="checkbox" {{ ip.is_dynamic|yesno:'checked,' }}></td>
</tr>
{% empty %}

8
ip_pool/templates/ip_pool/net_edit.html

@ -31,6 +31,10 @@
<button class="btn btn-primary">
<span class="glyphicon glyphicon-save"></span> {% trans 'Save' %}
</button>
<a href="{% url 'ip_pool:net_delete' object.pk %}" class="btn btn-danger btn-modal">
<span class="glyphicon glyphicon-remove"></span>
<span class="hidden-sm hidden-xs">{% trans 'Remove' %}</span>
</a>
<a href="{% back_url request %}" class="btn btn-default">
<span class="glyphicon glyphicon-backward"></span> {% trans 'Back' %}
</a>
@ -38,6 +42,10 @@
<span class="glyphicon glyphicon-eye-open"></span>
<span class="hidden-xs hidden-sm">{% trans 'View employed' %}</span>
</a>
<a href="{% url 'ip_pool:net_groups' object.pk %}" class="btn btn-default">
<span class="glyphicon glyphicon-user"></span>
<span class="hidden-xs hidden-sm">{% trans 'Groups available' %}</span>
</a>
</div>
</div>
</div>

33
ip_pool/templates/ip_pool/network_groups_available.html

@ -0,0 +1,33 @@
{% extends request.is_ajax|yesno:'bajax.html,base.html' %}
{% load i18n %}
{% block breadcrumb %}
<ol class="breadcrumb">
<li><span class="glyphicon glyphicon-home"></span></li>
<li><a href="{% url 'ip_pool:networks' %}">{% trans 'Ip pool' %}</a></li>
<li><a href="{% url 'ip_pool:net_edit' object.id %}">{{ object }}</a></li>
<li class="active">{% trans 'Belonging networks for groups' %}</li>
</ol>
{% endblock %}
{% block page-header %}
<h3>{% trans 'Make that pool available in specified groups' %}</h3>
{% endblock %}
{% block main %}
<form action="{% url 'ip_pool:net_groups' object.pk %}" method="post">{% csrf_token %}
{% for group in groups %}
<div class="checkbox">
<label>
{% if group.pk in selected_grps %}
<input name="gr" type="checkbox" value="{{ group.pk }}" checked/>
{% else %}
<input name="gr" type="checkbox" value="{{ group.pk }}"/>
{% endif %}
{{ group }}
</label>
</div>
{% endfor %}
<input type="submit" class="btn btn-primary" value="{% trans 'Save' %}">
</form>
{% endblock %}

23
ip_pool/templates/ip_pool/network_list.html

@ -19,17 +19,19 @@
<tr>
<th class="col-sm-3">{% trans 'Network' %}</th>
<th class="col-sm-3">{% trans 'Kind' %}</th>
<th class="col-sm-5">{% trans 'Description' %}</th>
<th class="col-sm-4">{% trans 'Description' %}</th>
<th class="col-sm-2">{% trans 'Scope' %}</th>
<th class="col-sm-1"></th>
</tr>
</thead>
<tbody>
{% with can_ch_net=perms.ip_pool.change_networkmodel %}
{% with can_ch_net=perms.ip_pool.change_networkmodel can_del_net=perms.ip_poo.delete_networkmodel %}
{% for netw in networks_list %}
<tr>
<td><a href="{% url 'ip_pool:ip_leases_list' netw.id %}">{{ netw }}</a></td>
<td><a href="{% url 'ip_pool:ip_leases_list' netw.id %}">{{ netw.get_network }}</a></td>
<td>{{ netw.get_kind_display }}</td>
<td>{{ netw.description }}</td>
<td>{{ netw.get_scope }}</td>
<td class="btn-group btn-group-sm btn-group-justified">
{% if can_ch_net %}
<a href="{% url 'ip_pool:net_edit' netw.pk %}" class="btn btn-primary">
@ -42,6 +44,17 @@
<span class="hidden-xs hidden-sm">{% trans 'Edit' %}</span>
</a>
{% endif %}
{% if can_del_net %}
<a href="{% url 'ip_pool:net_delete' netw.pk %}" class="btn btn-danger btn-modal">
<span class="glyphicon glyphicon-remove"></span>
<span class="hidden-xs hidden-sm">{% trans 'Remove' %}</span>
</a>
{% else %}
<a href="#" class="btn btn-danger" disabled>
<span class="glyphicon glyphicon-remove"></span>
<span class="hidden-xs hidden-sm">{% trans 'Permission denied' %}</span>
</a>
{% endif %}
<a href="{% url 'ip_pool:ip_leases_list' netw.pk %}" class="btn btn-default">
<span class="glyphicon glyphicon-eye-open"></span>
<span class="hidden-xs hidden-sm">{% trans 'View employed' %}</span>
@ -50,14 +63,14 @@
</tr>
{% empty %}
<tr>
<td colspan="4">{% trans 'You have not any networks available' %}</td>
<td colspan="5">{% trans 'You have not any networks available' %}</td>
</tr>
{% endfor %}
{% endwith %}
</tbody>
<tfoot>
<tr>
<td colspan="4">
<td colspan="5">
<a href="{% url 'ip_pool:net_add' %}" class="btn btn-success">
<span class="glyphicon glyphicon-plus"></span>
{% trans 'Add' %}

19
ip_pool/templates/ip_pool/networkmodel_confirm_delete.html

@ -0,0 +1,19 @@
{% extends 'base_delete_modal.html' %}
{% load i18n %}
{% block modal_form_url %}
{% url 'ip_pool:net_delete' object.pk %}
{% endblock %}
{% block modal_form_title %}
{% trans 'Remove network' %}
{% endblock %}
{% block modal_form_text %}
<h4>{% blocktrans trimmed with network_name=object %}
To delete network '{{ network_name }}'?
{% endblocktrans %}</h4>
<p>{% blocktrans trimmed %}
Attention! All leases in that network will be removed and services finished.
{% endblocktrans %}</p>
{% endblock %}

4
ip_pool/urls.py

@ -8,7 +8,9 @@ urlpatterns = [
url('^$', views.NetworksListView.as_view(), name='networks'),
url('^network_add/$', views.NetworkCreateView.as_view(), name='net_add'),
url('^(?P<net_id>\d{1,6})/$', views.IpLeasesListView.as_view(), name='ip_leases_list'),
url('^(?P<net_id>\d{1,6})/edit$', views.NetworkUpdateView.as_view(), name='net_edit'),
url('^(?P<net_id>\d{1,6})/edit/$', views.NetworkUpdateView.as_view(), name='net_edit'),
url('^(?P<net_id>\d{1,6})/del/$', views.NetworkDeleteView.as_view(), name='net_delete'),
url('^(?P<net_id>\d{1,6})/group_attach/$', views.network_in_groups, name='net_groups')
]
for dev_kind_code, _ in models.NetworkModel.NETWORK_KINDS:

37
ip_pool/views.py

@ -1,13 +1,15 @@
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from django.shortcuts import get_object_or_404
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse_lazy
from django.utils.decorators import method_decorator
from django.utils.translation import gettext_lazy as _
from django.views.generic import UpdateView, CreateView
from django.views.generic import UpdateView, CreateView, DeleteView
from guardian.decorators import permission_required_or_403 as permission_required
from djing.global_base_views import OrderedFilteredList
from ip_pool import models, forms
from group_app.models import Group
@method_decorator(login_required, name='dispatch')
@ -38,6 +40,18 @@ class NetworkUpdateView(UpdateView):
return r
@method_decorator(login_required, name='dispatch')
@method_decorator(permission_required('ip_pool.delete_networkmodel'), name='dispatch')
class NetworkDeleteView(DeleteView):
model = models.NetworkModel
pk_url_kwarg = 'net_id'
success_url = reverse_lazy('ip_pool:networks')
def delete(self, request, *args, **kwargs):
messages.success(request, _('Network has been deleted'))
return super(NetworkDeleteView, self).delete(request, *args, **kwargs)
@method_decorator(login_required, name='dispatch')
class IpLeasesListView(OrderedFilteredList):
template_name = 'ip_pool/ip_leases_list.html'
@ -65,3 +79,22 @@ class NetworkCreateView(CreateView):
r = super().form_valid(form)
messages.success(self.request, _('Network has been created'))
return r
@login_required
def network_in_groups(request, net_id):
network = get_object_or_404(models.NetworkModel, pk=net_id)
if request.method == 'POST':
gr = request.POST.getlist('gr')
network.groups.clear()
network.groups.add(*gr)
network.save()
messages.success(request, _('Successfully saved'))
return redirect('ip_pool:net_groups', net_id)
selected_grps = tuple(pk[0] for pk in network.groups.only('pk').values_list('pk'))
return render(request, 'ip_pool/network_groups_available.html', {
'object': network,
'selected_grps': selected_grps,
'groups': Group.objects.all().iterator()
})
Loading…
Cancel
Save