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

11
abonapp/models.py

@ -138,8 +138,6 @@ class Abon(BaseAccount):
('can_add_ballance', _('fill account')), ('can_add_ballance', _('fill account')),
('can_ping', _('Can ping')) ('can_ping', _('Can ping'))
) )
# TODO: Fix when duplicates already in database
# unique_together = ('device', 'dev_port')
verbose_name = _('Abon') verbose_name = _('Abon')
verbose_name_plural = _('Abons') verbose_name_plural = _('Abons')
ordering = ('fio',) ordering = ('fio',)
@ -251,6 +249,15 @@ class Abon(BaseAccount):
def get_absolute_url(self): def get_absolute_url(self):
return resolve_url('abonapp:abon_home', self.group.id, self.username) 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): class PassportInfo(models.Model):
series = models.CharField(_('Pasport serial'), max_length=4, validators=(validators.integer_validator,)) series = models.CharField(_('Pasport serial'), max_length=4, validators=(validators.integer_validator,))

18
abonapp/templates/abonapp/editAbon.html

@ -220,10 +220,20 @@
{% endfor %} {% endfor %}
</ul> </ul>
<div class="panel-footer"> <div class="panel-footer">
<a href="#" class="btn btn-success btn-sm" {{ abon.is_dynamic_ip|yesno:'disabled,' }}>
<span class="glyphicon glyphicon-plus"></span>
<span class="hidden-xs">{% trans 'Add' %}</span>
</a>
<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>
</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'^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$', 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+)/$', 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 = [ group_patterns = [
@ -42,6 +43,7 @@ group_patterns = [
url(r'^street/add$', views.street_add, name='street_add'), url(r'^street/add$', views.street_add, name='street_add'),
url(r'^street/edit', views.street_edit, name='street_edit'), 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'^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)) url(r'^(?P<uname>\w{1,127})/', include(subscriber_patterns))
] ]

53
abonapp/views.py

@ -1,4 +1,5 @@
from typing import Dict, Optional from typing import Dict, Optional
from datetime import datetime, date, timedelta
from django.contrib.gis.shortcuts import render_to_text from django.contrib.gis.shortcuts import render_to_text
from django.core.exceptions import PermissionDenied, ValidationError from django.core.exceptions import PermissionDenied, ValidationError
from django.db import IntegrityError, ProgrammingError, transaction from django.db import IntegrityError, ProgrammingError, transaction
@ -22,11 +23,12 @@ from agent import NasFailedResult, Transmitter, NasNetworkError
from . import forms from . import forms
from . import models from . import models
from devapp.models import Device, Port as DevPort from devapp.models import Device, Port as DevPort
from datetime import datetime, date, timedelta
from taskapp.models import Task from taskapp.models import Task
from dialing_app.models import AsteriskCDR from dialing_app.models import AsteriskCDR
from statistics.models import getModel from statistics.models import getModel
from group_app.models import Group 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.shortcuts import get_objects_for_user, assign_perm
from guardian.decorators import permission_required_or_403 as permission_required from guardian.decorators import permission_required_or_403 as permission_required
from djing import ping from djing import ping
@ -545,7 +547,7 @@ def chgroup_tariff(request, gid):
if request.method == 'POST': if request.method == 'POST':
tr = request.POST.getlist('tr') tr = request.POST.getlist('tr')
grp.tariff_set.clear() grp.tariff_set.clear()
grp.tariff_set.add(*(int(d) for d in tr))
grp.tariff_set.add(*tr)
grp.save() grp.save()
messages.success(request, _('Successfully saved')) messages.success(request, _('Successfully saved'))
return redirect('abonapp:ch_group_tariff', gid) return redirect('abonapp:ch_group_tariff', gid)
@ -836,6 +838,15 @@ def street_del(request, gid, sid):
return redirect('abonapp:people_list', gid) 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 @login_required
@permission_required('abonapp.can_view_additionaltelephones') @permission_required('abonapp.can_view_additionaltelephones')
@permission_required('group_app.can_view_group', (Group, 'pk', 'gid')) @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) 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 # API's
@login_required @login_required
@lib.decorators.only_admins @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) abon = Abon.objects.get(device=dev)
if not abon.is_dynamic_ip: if not abon.is_dynamic_ip:
return 'User settings is not dynamic' 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(): if abon.is_access():
abon.sync_with_nas(created=False) abon.sync_with_nas(created=False)
else: 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: except Abon.DoesNotExist:
return "User with device with mac '%s' does not exist" % switch_mac return "User with device with mac '%s' does not exist" % switch_mac
except Device.DoesNotExist: 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.utils.translation import gettext_lazy as _
from django.db import IntegrityError from django.db import IntegrityError
from djing.lib import DuplicateEntry
from djing.lib.tln.tln import ValidationError as TlnValidationError from djing.lib.tln.tln import ValidationError as TlnValidationError
from . import models from . import models
from djing import MAC_ADDR_REGEX, IP_ADDR_REGEX from djing import MAC_ADDR_REGEX, IP_ADDR_REGEX
@ -82,6 +83,6 @@ class PortForm(forms.ModelForm):
super(PortForm, self).save(commit) super(PortForm, self).save(commit)
except IntegrityError as e: except IntegrityError as e:
if "Duplicate entry" in str(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: else:
raise models.DeviceDBException(e) 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 devapp.base_intr import DeviceImplementationError
from djing.lib.decorators import only_admins, hash_auth_view 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 abonapp.models import Abon
from djing.lib.tln import ZteOltConsoleError, OnuZteRegisterError, ZteOltLoginFailed from djing.lib.tln import ZteOltConsoleError, OnuZteRegisterError, ZteOltLoginFailed
from group_app.models import Group from group_app.models import Group
@ -386,7 +386,7 @@ def edit_single_port(request, group_id, device_id, port_id):
}, request=request) }, request=request)
except Port.DoesNotExist: except Port.DoesNotExist:
messages.error(request, _('Port does not exist')) messages.error(request, _('Port does not exist'))
except DeviceDBException as e:
except (DeviceDBException, DuplicateEntry) as e:
messages.error(request, e) messages.error(request, e)
return redirect('devapp:manage_ports', group_id, device_id) return redirect('devapp:manage_ports', group_id, device_id)
@ -416,7 +416,7 @@ def add_single_port(request, group_id, device_id):
}, request=request) }, request=request)
except Device.DoesNotExist: except Device.DoesNotExist:
messages.error(request, _('Device does not exist')) messages.error(request, _('Device does not exist'))
except DeviceDBException as e:
except (DeviceDBException, DuplicateEntry) as e:
messages.error(request, e) messages.error(request, e)
return redirect('devapp:manage_ports', group_id, device_id) 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: if s is not None:
s.close() s.close()
return wrapped 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() value = value.strip()
if ':' in value: if ':' in value:
ip = ip_network(value) ip = ip_network(value)
return ip.compressed
return ip.with_prefixlen
return value return value
def get_db_prep_value(self, value, connection, prepared=False): 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 import forms
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.utils.translation import ugettext as _
from ip_pool import models from ip_pool import models
@ -21,3 +22,32 @@ class NetworkForm(forms.ModelForm):
class Meta: class Meta:
model = models.NetworkModel model = models.NetworkModel
fields = '__all__' 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 datetime import timedelta
from ipaddress import ip_network, ip_address from ipaddress import ip_network, ip_address
from typing import AnyStr
from django.conf import settings from django.conf import settings
from django.db.utils import IntegrityError
from django.shortcuts import resolve_url from django.shortcuts import resolve_url
from django.core.exceptions import ValidationError, ImproperlyConfigured from django.core.exceptions import ValidationError, ImproperlyConfigured
from django.db import models from django.db import models
from django.utils.timezone import now from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _ 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 ip_pool.fields import GenericIpAddressWithPrefix
from group_app.models import Group
class NetworkModel(models.Model): 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') kind = models.CharField(_('Kind of network'), max_length=6, choices=NETWORK_KINDS, default='guest')
description = models.CharField(_('Description'), max_length=64) 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): 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): def get_network(self):
if self.network is None:
return
if self._netw_cache is None: if self._netw_cache is None:
self._netw_cache = ip_network(self.network) self._netw_cache = ip_network(self.network)
return self._netw_cache return self._netw_cache
@ -41,35 +52,104 @@ class NetworkModel(models.Model):
def get_absolute_url(self): def get_absolute_url(self):
return resolve_url('ip_pool:net_edit', self.pk) 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: class Meta:
db_table = 'ip_pool_network' db_table = 'ip_pool_network'
verbose_name = _('Network') verbose_name = _('Network')
verbose_name_plural = _('Networks') verbose_name_plural = _('Networks')
ordering = ('description',)
ordering = ('network',)
class IpLeaseManager(models.Manager): class IpLeaseManager(models.Manager):
def get_free_ip(self, network: NetworkModel): 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( return self.create(
ip=ip, ip=ip,
network=netw_instance,
is_dynamic=True,
network=net,
is_dynamic=is_dynamic,
is_active=True is_active=True
) )
except IntegrityError as e:
if 'Duplicate entry' in str(e):
raise DuplicateEntry(_('Ip has already taken'))
raise e
def expired(self): def expired(self):
lease_live_time = getattr(settings, 'LEASE_LIVE_TIME') lease_live_time = getattr(settings, 'LEASE_LIVE_TIME')
@ -104,9 +184,8 @@ class IpLeaseModel(models.Model):
def clean(self): def clean(self):
ip = ip_address(self.ip) ip = ip_address(self.ip)
network = self.network.get_network() network = self.network.get_network()
if ip not in 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, 'ip': ip,
'net': network 'net': network
}, code='invalid') }, code='invalid')

2
ip_pool/templates/ip_pool/ip_leases_list.html

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

8
ip_pool/templates/ip_pool/net_edit.html

@ -31,6 +31,10 @@
<button class="btn btn-primary"> <button class="btn btn-primary">
<span class="glyphicon glyphicon-save"></span> {% trans 'Save' %} <span class="glyphicon glyphicon-save"></span> {% trans 'Save' %}
</button> </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"> <a href="{% back_url request %}" class="btn btn-default">
<span class="glyphicon glyphicon-backward"></span> {% trans 'Back' %} <span class="glyphicon glyphicon-backward"></span> {% trans 'Back' %}
</a> </a>
@ -38,6 +42,10 @@
<span class="glyphicon glyphicon-eye-open"></span> <span class="glyphicon glyphicon-eye-open"></span>
<span class="hidden-xs hidden-sm">{% trans 'View employed' %}</span> <span class="hidden-xs hidden-sm">{% trans 'View employed' %}</span>
</a> </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> </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> <tr>
<th class="col-sm-3">{% trans 'Network' %}</th> <th class="col-sm-3">{% trans 'Network' %}</th>
<th class="col-sm-3">{% trans 'Kind' %}</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> <th class="col-sm-1"></th>
</tr> </tr>
</thead> </thead>
<tbody> <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 %} {% for netw in networks_list %}
<tr> <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.get_kind_display }}</td>
<td>{{ netw.description }}</td> <td>{{ netw.description }}</td>
<td>{{ netw.get_scope }}</td>
<td class="btn-group btn-group-sm btn-group-justified"> <td class="btn-group btn-group-sm btn-group-justified">
{% if can_ch_net %} {% if can_ch_net %}
<a href="{% url 'ip_pool:net_edit' netw.pk %}" class="btn btn-primary"> <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> <span class="hidden-xs hidden-sm">{% trans 'Edit' %}</span>
</a> </a>
{% endif %} {% 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"> <a href="{% url 'ip_pool:ip_leases_list' netw.pk %}" class="btn btn-default">
<span class="glyphicon glyphicon-eye-open"></span> <span class="glyphicon glyphicon-eye-open"></span>
<span class="hidden-xs hidden-sm">{% trans 'View employed' %}</span> <span class="hidden-xs hidden-sm">{% trans 'View employed' %}</span>
@ -50,14 +63,14 @@
</tr> </tr>
{% empty %} {% empty %}
<tr> <tr>
<td colspan="4">{% trans 'You have not any networks available' %}</td>
<td colspan="5">{% trans 'You have not any networks available' %}</td>
</tr> </tr>
{% endfor %} {% endfor %}
{% endwith %} {% endwith %}
</tbody> </tbody>
<tfoot> <tfoot>
<tr> <tr>
<td colspan="4">
<td colspan="5">
<a href="{% url 'ip_pool:net_add' %}" class="btn btn-success"> <a href="{% url 'ip_pool:net_add' %}" class="btn btn-success">
<span class="glyphicon glyphicon-plus"></span> <span class="glyphicon glyphicon-plus"></span>
{% trans 'Add' %} {% 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('^$', views.NetworksListView.as_view(), name='networks'),
url('^network_add/$', views.NetworkCreateView.as_view(), name='net_add'), 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})/$', 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: 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.auth.decorators import login_required
from django.contrib import messages 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.decorators import method_decorator
from django.utils.translation import gettext_lazy as _ 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 guardian.decorators import permission_required_or_403 as permission_required
from djing.global_base_views import OrderedFilteredList from djing.global_base_views import OrderedFilteredList
from ip_pool import models, forms from ip_pool import models, forms
from group_app.models import Group
@method_decorator(login_required, name='dispatch') @method_decorator(login_required, name='dispatch')
@ -38,6 +40,18 @@ class NetworkUpdateView(UpdateView):
return r 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') @method_decorator(login_required, name='dispatch')
class IpLeasesListView(OrderedFilteredList): class IpLeasesListView(OrderedFilteredList):
template_name = 'ip_pool/ip_leases_list.html' template_name = 'ip_pool/ip_leases_list.html'
@ -65,3 +79,22 @@ class NetworkCreateView(CreateView):
r = super().form_valid(form) r = super().form_valid(form)
messages.success(self.request, _('Network has been created')) messages.success(self.request, _('Network has been created'))
return r 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