Browse Source

Change ip_field in abon model

devel
Dmitry Novikov 8 years ago
parent
commit
7cf7138a6e
  1. 20
      abonapp/locale/ru/LC_MESSAGES/django.po
  2. 51
      abonapp/migrations/0006_change_ip.py
  3. 46
      abonapp/models.py
  4. 83
      abonapp/templates/abonapp/editAbon.html
  5. 24
      abonapp/templates/abonapp/modal_add_lease.html
  6. 4
      abonapp/urls.py
  7. 167
      abonapp/views.py
  8. 6
      accounts_app/templatetags/acc_tags.py
  9. 37
      agent/commands/dhcp.py
  10. 2
      dhcp_lever.py
  11. 8
      djing/lib/auth_backends.py
  12. 9
      djing/lib/mixins.py
  13. 1
      ip_pool/admin.py
  14. 33
      ip_pool/forms.py
  15. 37
      ip_pool/migrations/0003_add_leases_history_model.py
  16. 31
      ip_pool/models.py
  17. 44
      ip_pool/templates/ip_pool/ip_leases_list.html
  18. 4
      ip_pool/templates/ip_pool/net_edit.html
  19. 6
      ip_pool/templates/ip_pool/network_list.html
  20. 1
      ip_pool/urls.py
  21. 17
      ip_pool/views.py
  22. 36
      nas_app/nas_managers/core.py
  23. 112
      nas_app/nas_managers/mod_mikrotik.py
  24. 30
      nas_app/nas_managers/structs.py
  25. 10
      periodic.py
  26. 2
      searchapp/views.py

20
abonapp/locale/ru/LC_MESSAGES/django.po

@ -116,8 +116,8 @@ msgid "User group"
msgstr "Группа" msgstr "Группа"
#: models.py:96 templates/abonapp/editAbon.html:180 #: models.py:96 templates/abonapp/editAbon.html:180
msgid "Ip addresses"
msgstr "IP Адреса"
msgid "Ip address"
msgstr "IP Адрес"
#: models.py:103 #: models.py:103
msgid "Network access server" msgid "Network access server"
@ -202,11 +202,6 @@ msgstr "Не хватает денег на счету"
msgid "Buy service default log" msgid "Buy service default log"
msgstr "Покупка тарифного плана через админку" msgstr "Покупка тарифного плана через админку"
#: models.py:228
#, python-format
msgid "Account \"%(username)s\" not have any active leases"
msgstr "Учётная запись \"%(username)s\" не имеет ни одной активной сессии"
#: models.py:238 models.py:255 models.py:272 views.py:684 views.py:1132 #: models.py:238 models.py:255 models.py:272 views.py:684 views.py:1132
#: views.py:1175 #: views.py:1175
msgid "NAS required" msgid "NAS required"
@ -515,10 +510,6 @@ msgstr "Выделен из:"
msgid "From" msgid "From"
msgstr "От" msgstr "От"
#: templates/abonapp/editAbon.html:218
msgid "Leases does not found"
msgstr "Аренды ip не найдены"
#: templates/abonapp/editAbon.html:230 #: templates/abonapp/editAbon.html:230
#: templates/abonapp/modal_add_lease.html:16 #: templates/abonapp/modal_add_lease.html:16
#: templates/abonapp/modal_add_phone.html:27 #: templates/abonapp/modal_add_phone.html:27
@ -529,8 +520,8 @@ msgid "Add"
msgstr "Добавить" msgstr "Добавить"
#: templates/abonapp/editAbon.html:234 #: templates/abonapp/editAbon.html:234
msgid "Active networks"
msgstr "Активные подсети"
msgid "Networks"
msgstr "Подсети"
#: templates/abonapp/editAbon.html:242 #: templates/abonapp/editAbon.html:242
msgid "User flags" msgid "User flags"
@ -1159,3 +1150,6 @@ msgstr "Автопродление услуги."
msgid "No author attached" msgid "No author attached"
msgstr "Автор не назначен" msgstr "Автор не назначен"
msgid "User not have ip"
msgstr "У пользователя нет ip"

51
abonapp/migrations/0006_change_ip.py

@ -0,0 +1,51 @@
# Generated by Django 2.1 on 2018-10-10 16:11
# from json import dump
from django.db import migrations, models
TMP_FILE = '/tmp/migrate_ip.json'
DUMP = []
def backup_info(apps, _):
Abon = apps.get_model('abonapp', 'Abon')
abons = Abon.objects.annotate(
addr_count=models.Count('ip_addresses')
).filter(addr_count__gt=0).only('ip_addresses').iterator()
global DUMP
for abon in abons:
ip_addr = abon.ip_addresses.first()
DUMP.append({
'pk': abon.pk,
'addr': ip_addr.ip
})
# with open(TMP_FILE, 'w') as f:
# dump(r, f, indent=2)
def restore_ips(apps, _):
Abon = apps.get_model('abonapp', 'Abon')
for abon in DUMP:
Abon.objects.filter(pk=abon.get('pk')).update(ip_address=abon.get('addr'))
class Migration(migrations.Migration):
dependencies = [
('abonapp', '0005_current_tariff'),
]
operations = [
migrations.RunPython(backup_info),
migrations.RemoveField(
model_name='abon',
name='ip_addresses',
),
migrations.AddField(
model_name='abon',
name='ip_address',
field=models.GenericIPAddressField(blank=True, null=True, unique=True, verbose_name='Ip address'),
),
migrations.RunPython(restore_ips)
]

46
abonapp/models.py

@ -16,7 +16,7 @@ from accounts_app.models import UserProfile, MyUserManager, BaseAccount
from nas_app.nas_managers import AbonStruct, TariffStruct, NasFailedResult, NasNetworkError from nas_app.nas_managers import AbonStruct, TariffStruct, NasFailedResult, NasNetworkError
from group_app.models import Group from group_app.models import Group
from djing.lib import LogicError from djing.lib import LogicError
from ip_pool.models import IpLeaseModel, NetworkModel
from ip_pool.models import NetworkModel
from tariff_app.models import Tariff, PeriodicPay from tariff_app.models import Tariff, PeriodicPay
from bitfield import BitField from bitfield import BitField
@ -90,7 +90,7 @@ class Abon(BaseAccount):
current_tariff = models.OneToOneField(AbonTariff, null=True, blank=True, on_delete=models.SET_NULL, default=None) current_tariff = models.OneToOneField(AbonTariff, null=True, blank=True, on_delete=models.SET_NULL, default=None)
group = models.ForeignKey(Group, on_delete=models.SET_NULL, blank=True, null=True, verbose_name=_('User group')) group = models.ForeignKey(Group, on_delete=models.SET_NULL, blank=True, null=True, verbose_name=_('User group'))
ballance = models.FloatField(default=0.0) ballance = models.FloatField(default=0.0)
ip_addresses = models.ManyToManyField(IpLeaseModel, verbose_name=_('Ip addresses'))
ip_address = models.GenericIPAddressField(verbose_name=_('Ip address'), unique=True, null=True, blank=True)
description = models.TextField(_('Comment'), null=True, blank=True) description = models.TextField(_('Comment'), null=True, blank=True)
street = models.ForeignKey(AbonStreet, on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_('Street')) street = models.ForeignKey(AbonStreet, on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_('Street'))
house = models.CharField(_('House'), max_length=12, null=True, blank=True) house = models.CharField(_('House'), max_length=12, null=True, blank=True)
@ -198,6 +198,25 @@ class Abon(BaseAccount):
comment=comment or _('Buy service default log') comment=comment or _('Buy service default log')
) )
def attach_ip_addr(self, ip, strict=False):
"""
Attach ip address to account
:param ip: Instance of str or ip_address
:param strict: If strict is True then ip not replaced quietly
:return: None
"""
if strict and self.ip_address:
raise LogicError('Ip address already exists')
self.ip_address = ip
self.save(update_fields=('ip_address',))
def free_ip_addr(self) -> bool:
if self.ip_address:
self.ip_address = None
self.save(update_fields=('ip_address',))
return True
return False
# is subscriber have access to service, view in tariff_app.custom_tariffs.<TariffBase>.manage_access() # is subscriber have access to service, view in tariff_app.custom_tariffs.<TariffBase>.manage_access()
def is_access(self) -> bool: def is_access(self) -> bool:
if not self.is_active: if not self.is_active:
@ -210,22 +229,17 @@ class Abon(BaseAccount):
return ct.manage_access(self) return ct.manage_access(self)
# make subscriber from agent structure # make subscriber from agent structure
def build_agent_struct(self, raise_errs=True):
abon_addresses = tuple(ip_address(i.ip) for i in self.ip_addresses.filter(is_active=True))
# if not abon_addresses:
# return
def build_agent_struct(self):
if not self.ip_address:
return
abon_address = ip_address(self.ip_address)
abon_tariff = self.active_tariff() abon_tariff = self.active_tariff()
if abon_tariff is None: if abon_tariff is None:
agent_trf = None agent_trf = None
else: else:
trf = abon_tariff.tariff trf = abon_tariff.tariff
agent_trf = TariffStruct(trf.id, trf.speedIn, trf.speedOut) agent_trf = TariffStruct(trf.id, trf.speedIn, trf.speedOut)
if len(abon_addresses) > 0:
return AbonStruct(self.pk, abon_addresses, agent_trf, self.is_access())
if raise_errs:
raise LogicError(_('Account "%(username)s" not have any active leases') % {
'username': self.username
})
return AbonStruct(self.pk, abon_address, agent_trf, self.is_access())
def nas_sync_self(self) -> Optional[Exception]: def nas_sync_self(self) -> Optional[Exception]:
""" """
@ -281,14 +295,6 @@ 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, network: Optional[NetworkModel], mac_addr=None):
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, net=network, mac=mac_addr)
if lease is None:
return 'Error while creating a ip lease'
self.ip_addresses.add(lease)
def enable_service(self, tariff: Tariff, deadline=None, time_start=None): def enable_service(self, tariff: Tariff, deadline=None, time_start=None):
""" """
Makes a services for current user Makes a services for current user

83
abonapp/templates/abonapp/editAbon.html

@ -46,9 +46,6 @@
<script type="text/javascript"> <script type="text/javascript">
$(function () { $(function () {
$("#iprefreshbtn").on('click', function(){
$("#{{ form.ip_address.id_for_label }}").val('');
});
$('#passwdtoggler').on('mousedown', function(){ $('#passwdtoggler').on('mousedown', function(){
document.getElementById("{{ form.password.id_for_label }}").type='text'; document.getElementById("{{ form.password.id_for_label }}").type='text';
}).on('mouseup', function(){ }).on('mouseup', function(){
@ -182,61 +179,45 @@
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<h3 class="panel-title">{% trans 'Ip addresses' %}</h3>
<h3 class="panel-title">{% trans 'Ip address' %}</h3>
</div>
<div class="panel-body">
{% if abon.ip_address %}
<div class="btn-group btn-group-xs">
<a href="{% url 'abonapp:user_session_free' group.pk abon.username %}" class="btn btn-danger" title="{% trans 'Free session' %}" data-toggle="tooltip">
<span class="glyphicon glyphicon-remove"></span>
</a>
<b>{{ abon.ip_address }}</b>
{% if perms.abonapp.can_ping %}
<a href="{% url 'abonapp:ping' group.pk abon.username %}" class="btn btn-default btn-cmd" title="Ping" data-param="{{ abon.ip_address }}">
<span class="glyphicon glyphicon-flash"></span> Ping
</a>
{% else %}
<a href="#" class="btn btn-default disabled" title="{% trans 'Permission denied' %}">
<span class="glyphicon glyphicon-flash"></span> Ping
</a>
{% endif %}
</div>
{% else %}
<span class="text-info">{% trans 'No ip address' %}</span>
{% endif %}
</div> </div>
<ul class="list-group">
{% with can_ping=perms.abonapp.can_ping %}
{% for lease in abon.ip_addresses.all %}
<li class="list-group-item">
<div class="btn-group btn-group-xs">
{% if lease.is_active %}
<a href="{% url 'abonapp:user_session_free' group.pk abon.username lease.pk %}" class="btn btn-danger" title="{% trans 'Free session' %}" data-toggle="tooltip">
<span class="glyphicon glyphicon-remove"></span>
</a>
{% else %}
<a href="{% url 'abonapp:user_session_start' group.pk abon.username lease.pk %}" class="btn btn-success" title="{% trans 'Start session' %}" data-toggle="tooltip">
<span class="glyphicon glyphicon-flash"></span>
</a>
{% endif %}
{% if can_ping %}
<a href="{% url 'abonapp:ping' group.pk abon.username %}" class="btn btn-default btn-cmd" title="Ping" data-param="{{ lease.ip }}">
<span class="glyphicon glyphicon-flash"></span> Ping
</a>
{% else %}
<a href="#" class="btn btn-default disabled" title="{% trans 'Permission denied' %}">
<span class="glyphicon glyphicon-flash"></span> Ping
</a>
{% endif %}
</div>
<span{{ lease.is_active|yesno:', class="text-muted"'|safe }}>
<b>{{ lease }}</b>
<small>{% trans 'Leased by:' %} {{ lease.lease_time|date:'d-m H:i:s' }}.
{% if lease.mac_addr %}
{% trans 'From' %}: {{ lease.mac_addr }}.
{% endif %}
</small>
</span>
</li>
{% empty %}
<li class="list-group-item">{% trans 'Leases does not found' %}</li>
{% endfor %}
{% endwith %}
</ul>
<div class="panel-footer"> <div class="panel-footer">
<div class="btn-group btn-group-sm"> <div class="btn-group btn-group-sm">
{% if abon.is_dynamic_ip %}
<a href="#" class="btn btn-success" disabled>
{% if abon.ip_address %}
<a href="#" class="btn btn-primary">
<span class="glyphicon glyphicon-edit"></span>
<span class="hidden-xs">{% trans 'Change' %}</span>
</a>
{% else %} {% else %}
<a href="{% url 'abonapp:lease_add' group.pk abon.username %}" class="btn btn-success btn-modal">
<a href="#" class="btn btn-success btn-modal">
<span class="glyphicon glyphicon-plus"></span>
<span class="hidden-xs">{% trans 'Add' %}</span>
</a>
{% endif %} {% 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"> <a href="{% url 'abonapp:active_nets' group.pk %}" class="btn btn-default btn-modal">
<span class="glyphicon glyphicon-globe"></span> <span class="glyphicon glyphicon-globe"></span>
<span class="hidden-sm hidden-xs">{% trans 'Active networks' %}</span>
<span class="hidden-sm hidden-xs">{% trans 'Networks' %}</span>
</a> </a>
</div> </div>
</div> </div>

24
abonapp/templates/abonapp/modal_add_lease.html

@ -1,24 +0,0 @@
{% extends request.is_ajax|yesno:'nullcont.htm,abonapp/ext.htm' %}
{% load i18n 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 %}

4
abonapp/urls.py

@ -26,12 +26,10 @@ subscriber_patterns = [
path('tel/add/', views.tel_add, name='telephone_new'), path('tel/add/', views.tel_add, name='telephone_new'),
path('tel/del/', views.tel_del, name='telephone_del'), path('tel/del/', views.tel_del, name='telephone_del'),
path('markers/', views.EditSibscriberMarkers.as_view(), name='markers_edit'), path('markers/', views.EditSibscriberMarkers.as_view(), name='markers_edit'),
path('session/<int:lease_id>/free/', views.user_session_toggle, {'action': 'free'}, name='user_session_free'),
path('session/<int:lease_id>/start/', views.user_session_toggle, {'action': 'start'}, name='user_session_start'),
path('session/free/', views.user_session_free, name='user_session_free'),
path('periodic_pay/', views.add_edit_periodic_pay, name='add_periodic_pay'), path('periodic_pay/', views.add_edit_periodic_pay, name='add_periodic_pay'),
path('periodic_pay/<int:periodic_pay_id>/', views.add_edit_periodic_pay, name='add_periodic_pay'), path('periodic_pay/<int:periodic_pay_id>/', views.add_edit_periodic_pay, name='add_periodic_pay'),
path('periodic_pay/<int:periodic_pay_id>/del/', views.del_periodic_pay, name='del_periodic_pay'), path('periodic_pay/<int:periodic_pay_id>/del/', views.del_periodic_pay, name='del_periodic_pay'),
path('lease/add/', views.lease_add, name='lease_add'),
path('ping/', views.abon_ping, name='ping'), path('ping/', views.abon_ping, name='ping'),
path('set_auto_continue_service/', views.set_auto_continue_service, name='set_auto_continue_service') path('set_auto_continue_service/', views.set_auto_continue_service, name='set_auto_continue_service')
] ]

167
abonapp/views.py

@ -1,4 +1,3 @@
from ipaddress import ip_address
from typing import Dict, Optional from typing import Dict, Optional
from datetime import datetime, date from datetime import datetime, date
from django.core.exceptions import PermissionDenied, ValidationError from django.core.exceptions import PermissionDenied, ValidationError
@ -6,6 +5,7 @@ from django.db import IntegrityError, ProgrammingError, transaction, Operational
from django.db.models import Count, Q from django.db.models import Count, Q
from django.shortcuts import render, redirect, get_object_or_404, resolve_url from django.shortcuts import render, redirect, get_object_or_404, resolve_url
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseRedirect from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseRedirect
from django.contrib import messages from django.contrib import messages
from django.urls import reverse_lazy from django.urls import reverse_lazy
@ -25,21 +25,22 @@ 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 ip_pool.models import NetworkModel
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 guardian.mixins import PermissionRequiredMixin
from djing import ping from djing import ping
from djing import lib from djing import lib
from djing.lib.decorators import json_view, only_admins from djing.lib.decorators import json_view, only_admins
from djing.lib.mixins import OnlyAdminsMixin
from djing.global_base_views import OrderedFilteredList, SecureApiView from djing.global_base_views import OrderedFilteredList, SecureApiView
login_decs = login_required, only_admins
class AbonappPermissionMixin(LoginRequiredMixin, OnlyAdminsMixin, PermissionRequiredMixin):
return_403 = True
@method_decorator(login_decs, name='dispatch')
class PeoplesListView(OrderedFilteredList):
class PeoplesListView(LoginRequiredMixin, OnlyAdminsMixin, OrderedFilteredList):
template_name = 'abonapp/peoples.html' template_name = 'abonapp/peoples.html'
def get_queryset(self): def get_queryset(self):
@ -77,8 +78,7 @@ class PeoplesListView(OrderedFilteredList):
return context return context
@method_decorator(login_decs, name='dispatch')
class GroupListView(OrderedFilteredList):
class GroupListView(LoginRequiredMixin, OnlyAdminsMixin, OrderedFilteredList):
context_object_name = 'groups' context_object_name = 'groups'
template_name = 'abonapp/group_list.html' template_name = 'abonapp/group_list.html'
queryset = Group.objects.annotate(usercount=Count('abon')) queryset = Group.objects.annotate(usercount=Count('abon'))
@ -90,9 +90,8 @@ class GroupListView(OrderedFilteredList):
return queryset return queryset
@method_decorator(login_decs, name='dispatch')
@method_decorator(permission_required('abonapp.add_abon'), name='dispatch')
class AbonCreateView(CreateView):
class AbonCreateView(AbonappPermissionMixin, CreateView):
permission_required = 'abonapp.add_abon'
group = None group = None
abon = None abon = None
form_class = forms.AbonForm form_class = forms.AbonForm
@ -146,9 +145,8 @@ class AbonCreateView(CreateView):
return super(AbonCreateView, self).form_invalid(form) return super(AbonCreateView, self).form_invalid(form)
@method_decorator(login_decs, name='dispatch')
@method_decorator(permission_required('abonapp.delete_abon'), name='dispatch')
class DelAbonDeleteView(DeleteView):
class DelAbonDeleteView(AbonappPermissionMixin, DeleteView):
permission_required = 'abonapp.delete_abon'
model = models.Abon model = models.Abon
slug_url_kwarg = 'uname' slug_url_kwarg = 'uname'
slug_field = 'username' slug_field = 'username'
@ -220,12 +218,14 @@ def abonamount(request, gid: int, uname):
}) })
@method_decorator(login_decs, name='dispatch')
@method_decorator(permission_required('group_app.view_group', (Group, 'pk', 'gid')), name='dispatch')
class DebtsListView(OrderedFilteredList):
class DebtsListView(AbonappPermissionMixin, OrderedFilteredList):
permission_required = 'group_app.view_group'
context_object_name = 'invoices' context_object_name = 'invoices'
template_name = 'abonapp/invoiceForPayment.html' template_name = 'abonapp/invoiceForPayment.html'
def get_permission_object(self):
return self.abon.group
def get_queryset(self): def get_queryset(self):
abon = get_object_or_404(models.Abon, username=self.kwargs.get('uname')) abon = get_object_or_404(models.Abon, username=self.kwargs.get('uname'))
self.abon = abon self.abon = abon
@ -238,12 +238,14 @@ class DebtsListView(OrderedFilteredList):
return context return context
@method_decorator(login_decs, name='dispatch')
@method_decorator(permission_required('group_app.view_group', (Group, 'pk', 'gid')), name='dispatch')
class PayHistoryListView(OrderedFilteredList):
class PayHistoryListView(AbonappPermissionMixin, OrderedFilteredList):
permission_required = 'group_app.view_group'
context_object_name = 'pay_history' context_object_name = 'pay_history'
template_name = 'abonapp/payHistory.html' template_name = 'abonapp/payHistory.html'
def get_permission_object(self):
return self.abon.group
def get_queryset(self): def get_queryset(self):
abon = get_object_or_404(models.Abon, username=self.kwargs.get('uname')) abon = get_object_or_404(models.Abon, username=self.kwargs.get('uname'))
self.abon = abon self.abon = abon
@ -283,9 +285,8 @@ def abon_services(request, gid: int, uname):
}) })
@method_decorator(login_decs, name='dispatch')
@method_decorator(permission_required('abonapp.view_abon'), name='post')
class AbonHomeUpdateView(UpdateView):
class AbonHomeUpdateView(AbonappPermissionMixin, UpdateView):
permission_required = 'abonapp.view_abon'
model = models.Abon model = models.Abon
form_class = forms.AbonForm form_class = forms.AbonForm
slug_field = 'username' slug_field = 'username'
@ -469,9 +470,8 @@ def unsubscribe_service(request, gid: int, uname, abon_tariff_id: int):
return redirect('abonapp:abon_services', gid=gid, uname=uname) return redirect('abonapp:abon_services', gid=gid, uname=uname)
@method_decorator(login_decs, name='dispatch')
@method_decorator(permission_required('abonapp.view_abonlog'), name='dispatch')
class LogListView(ListView):
class LogListView(AbonappPermissionMixin, ListView):
permission_required = 'abonapp.view_abonlog'
paginate_by = getattr(settings, 'PAGINATION_ITEMS_PER_PAGE', 10) paginate_by = getattr(settings, 'PAGINATION_ITEMS_PER_PAGE', 10)
http_method_names = ('get',) http_method_names = ('get',)
context_object_name = 'logs' context_object_name = 'logs'
@ -479,9 +479,8 @@ class LogListView(ListView):
model = models.AbonLog model = models.AbonLog
@method_decorator(login_decs, name='dispatch')
@method_decorator(permission_required('abonapp.view_invoiceforpayment'), name='dispatch')
class DebtorsListView(ListView):
class DebtorsListView(AbonappPermissionMixin, ListView):
permission_required = 'abonapp.view_invoiceforpayment'
paginate_by = getattr(settings, 'PAGINATION_ITEMS_PER_PAGE', 10) paginate_by = getattr(settings, 'PAGINATION_ITEMS_PER_PAGE', 10)
http_method_names = ('get',) http_method_names = ('get',)
context_object_name = 'invoices' context_object_name = 'invoices'
@ -489,14 +488,16 @@ class DebtorsListView(ListView):
queryset = models.InvoiceForPayment.objects.filter(status=True) queryset = models.InvoiceForPayment.objects.filter(status=True)
@method_decorator(login_decs, name='dispatch')
@method_decorator(permission_required('group_app.view_group', (Group, 'pk', 'gid')), name='dispatch')
class TaskLogListView(ListView):
class TaskLogListView(AbonappPermissionMixin, ListView):
permission_required = 'group_app.view_group'
paginate_by = getattr(settings, 'PAGINATION_ITEMS_PER_PAGE', 10) paginate_by = getattr(settings, 'PAGINATION_ITEMS_PER_PAGE', 10)
http_method_names = ('get',) http_method_names = ('get',)
context_object_name = 'tasks' context_object_name = 'tasks'
template_name = 'abonapp/task_log.html' template_name = 'abonapp/task_log.html'
def get_permission_object(self):
return self.abon.group
def get_queryset(self): def get_queryset(self):
abon = get_object_or_404(models.Abon, username=self.kwargs.get('uname')) abon = get_object_or_404(models.Abon, username=self.kwargs.get('uname'))
self.abon = abon self.abon = abon
@ -509,9 +510,8 @@ class TaskLogListView(ListView):
return context return context
@method_decorator(login_decs, name='dispatch')
@method_decorator(permission_required('abonapp.view_passportinfo'), name='dispatch')
class PassportUpdateView(UpdateView):
class PassportUpdateView(AbonappPermissionMixin, UpdateView):
permission_required = 'abonapp.view_passportinfo'
form_class = forms.PassportForm form_class = forms.PassportForm
model = models.PassportInfo model = models.PassportInfo
template_name = 'abonapp/modal_passport_view.html' template_name = 'abonapp/modal_passport_view.html'
@ -775,8 +775,7 @@ def vcards(r):
return response return response
@method_decorator(login_decs, name='dispatch')
class DialsListView(OrderedFilteredList):
class DialsListView(LoginRequiredMixin, OnlyAdminsMixin, OrderedFilteredList):
context_object_name = 'logs' context_object_name = 'logs'
template_name = 'abonapp/dial_log.html' template_name = 'abonapp/dial_log.html'
@ -1106,9 +1105,8 @@ def del_periodic_pay(request, gid: int, uname, periodic_pay_id):
return redirect('abonapp:abon_services', gid, uname) return redirect('abonapp:abon_services', gid, uname)
@method_decorator(login_decs, name='dispatch')
@method_decorator(permission_required('abonapp.change_abon'), name='dispatch')
class EditSibscriberMarkers(UpdateView):
class EditSibscriberMarkers(AbonappPermissionMixin, UpdateView):
permission_required = 'abonapp.change_abon'
http_method_names = ('get', 'post') http_method_names = ('get', 'post')
template_name = 'abonapp/modal_user_markers.html' template_name = 'abonapp/modal_user_markers.html'
form_class = forms.MarkersForm form_class = forms.MarkersForm
@ -1142,78 +1140,17 @@ class EditSibscriberMarkers(UpdateView):
@login_required @login_required
@only_admins @only_admins
@permission_required('abonapp.change_abon') @permission_required('abonapp.change_abon')
def user_session_toggle(request, gid: int, uname, lease_id: int, action=None):
def user_session_free(request, gid: int, uname):
abon = get_object_or_404(models.Abon, username=uname) abon = get_object_or_404(models.Abon, username=uname)
if abon.nas is None: if abon.nas is None:
messages.error(request, _('NAS required')) messages.error(request, _('NAS required'))
return redirect('abonapp:abon_home', gid, uname) return redirect('abonapp:abon_home', gid, uname)
lease = abon.ip_addresses.get(pk=lease_id)
tm = abon.nas.get_nas_manager()
try:
if action == 'free':
try:
abon_nas_obj = abon.build_agent_struct()
tm.lease_free(abon_nas_obj, ip_address(lease.ip))
messages.success(request, _('Ip lease has been freed'))
lease.free()
except lib.LogicError:
messages.error(request, _('You cannot disable last session'))
elif action == 'start':
lease.start()
abon_nas_obj = abon.build_agent_struct()
tm.lease_start(abon_nas_obj, ip_address(lease.ip))
messages.success(request, _('Ip lease has been started'))
else:
messages.error(request, _('Unexpected action'))
except (NasFailedResult, lib.LogicError) as e:
messages.error(request, e)
return redirect('abonapp:abon_home', gid, uname)
@login_required
@only_admins
@permission_required('abonapp.change_abon')
def lease_add(request, gid: int, 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)
if abon.nas is None:
messages.error(request, _('NAS required'))
else:
tm = abon.nas.get_nas_manager()
tm.lease_start(
user=abon.build_agent_struct(),
lease=lease.ip
)
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'))
if abon.ip_address:
abon.free_ip_addr()
messages.success(request, _('Ip lease has been freed'))
else: 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
})
messages.error(request, _('User not have ip'))
return redirect('abonapp:abon_home', gid, uname)
@login_required @login_required
@ -1247,9 +1184,8 @@ def abons(request):
ablist = ({ ablist = ({
'id': abn.pk, 'id': abn.pk,
'tarif_id': abn.active_tariff().tariff.pk if abn.active_tariff() is not None else 0, 'tarif_id': abn.active_tariff().tariff.pk if abn.active_tariff() is not None else 0,
'ips': ','.join(str(i.ip) for i in abn.ip_addresses.filter(is_active=True)),
'is_active': abn.is_active
} for abn in models.Abon.objects.all())
'ip': abn.ip_address
} for abn in models.Abon.objects.iterator())
tarlist = ({ tarlist = ({
'id': trf.pk, 'id': trf.pk,
@ -1285,10 +1221,13 @@ class DhcpLever(SecureApiView):
@method_decorator(json_view) @method_decorator(json_view)
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
data = request.GET.copy() data = request.GET.copy()
r = self.on_dhcp_event(data)
if r is not None:
return {'text': r}
return {'status': 'ok'}
try:
r = self.on_dhcp_event(data)
if r is not None:
return {'text': r}
return {'status': 'ok'}
except IntegrityError as e:
return {'status': str(e).replace('\n', ' ')}
@staticmethod @staticmethod
def on_dhcp_event(data: Dict) -> Optional[str]: def on_dhcp_event(data: Dict) -> Optional[str]:

6
accounts_app/templatetags/acc_tags.py

@ -3,7 +3,7 @@ from ipaddress import ip_address, AddressValueError
from django import template from django import template
from django.db.models import Model from django.db.models import Model
from django.apps import apps from django.apps import apps
from ip_pool.models import IpLeaseModel
from abonapp.models import Abon
from six import string_types, class_types from six import string_types, class_types
register = template.Library() register = template.Library()
@ -26,8 +26,8 @@ def can_login_by_location(request):
try: try:
remote_ip = ip_address(request.META.get('REMOTE_ADDR')) remote_ip = ip_address(request.META.get('REMOTE_ADDR'))
if remote_ip.version == 4: if remote_ip.version == 4:
has_leases = IpLeaseModel.objects.filter(ip=str(remote_ip), abon__is_active=True).exists()
return has_leases
has_exist = Abon.objects.filter(ip_address=str(remote_ip), is_active=True).exists()
return has_exist
except AddressValueError: except AddressValueError:
pass pass
return False return False

37
agent/commands/dhcp.py

@ -2,7 +2,6 @@ from typing import Optional
from django.core.exceptions import MultipleObjectsReturned from django.core.exceptions import MultipleObjectsReturned
from abonapp.models import Abon from abonapp.models import Abon
from devapp.models import Device, Port from devapp.models import Device, Port
from ip_pool.models import IpLeaseModel
def dhcp_commit(client_ip: str, client_mac: str, switch_mac: str, switch_port: int) -> Optional[str]: def dhcp_commit(client_ip: str, client_mac: str, switch_mac: str, switch_port: int) -> Optional[str]:
@ -13,22 +12,17 @@ def dhcp_commit(client_ip: str, client_mac: str, switch_mac: str, switch_port: i
if mngr_class.get_is_use_device_port(): if mngr_class.get_is_use_device_port():
abon = Abon.objects.get(dev_port__device=dev, abon = Abon.objects.get(dev_port__device=dev,
dev_port__num=switch_port, dev_port__num=switch_port,
device=dev)
device=dev, is_active=True)
else: else:
abon = Abon.objects.get(device=dev)
abon = Abon.objects.get(device=dev, is_active=True)
if not abon.is_dynamic_ip: if not abon.is_dynamic_ip:
return 'User settings is not dynamic' return 'User settings is not dynamic'
client_ips = tuple(str(ip) for ip in abon.ip_addresses.all())
if client_ip in client_ips:
return 'Ip address already existed'
add_lease_result = abon.add_lease(client_ip, mac_addr=client_mac, network=None)
if add_lease_result is None:
if abon.is_access():
abon.nas_sync_self()
else:
return 'User %s is not access to service' % abon.username
abon.attach_ip_addr(client_ip, strict=False)
if abon.is_access():
r = abon.nas_sync_self()
return r if r else None
else: else:
return add_lease_result
return 'User %s is not access to service' % abon.username
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:
@ -43,16 +37,13 @@ def dhcp_commit(client_ip: str, client_mac: str, switch_mac: str, switch_port: i
def dhcp_expiry(client_ip: str) -> Optional[str]: def dhcp_expiry(client_ip: str) -> Optional[str]:
try:
lease = IpLeaseModel.objects.get(ip=client_ip)
lease.is_active = False
lease.save(update_fields=('is_active',))
abon = Abon.objects.filter(ip_addresses=lease).first()
if abon is None:
return "Subscriber with ip %s does not exist" % client_ip
abon.nas_sync_self()
except IpLeaseModel.DoesNotExist:
pass
abon = Abon.objects.filter(ip_address=client_ip, is_active=True).exclude(current_tariff=None).first()
if abon is None:
return "Subscriber with ip %s does not exist" % client_ip
else:
is_freed = abon.free_ip_addr()
if is_freed:
abon.nas_sync_self()
def dhcp_release(client_ip: str) -> Optional[str]: def dhcp_release(client_ip: str) -> Optional[str]:

2
dhcp_lever.py

@ -5,7 +5,7 @@ from urllib.parse import urlencode
from urllib.request import urlopen from urllib.request import urlopen
from hashlib import sha256 from hashlib import sha256
API_AUTH_SECRET = 'your api key'
API_AUTH_SECRET = 'yourapikey'
SERVER_DOMAIN = 'http://localhost:8000' SERVER_DOMAIN = 'http://localhost:8000'

8
djing/lib/auth_backends.py

@ -3,7 +3,6 @@ from ipaddress import ip_address, AddressValueError
from django.contrib.auth.backends import ModelBackend from django.contrib.auth.backends import ModelBackend
from accounts_app.models import BaseAccount, UserProfile from accounts_app.models import BaseAccount, UserProfile
from abonapp.models import Abon from abonapp.models import Abon
from ip_pool.models import IpLeaseModel
class CustomAuthBackend(ModelBackend): class CustomAuthBackend(ModelBackend):
@ -40,13 +39,12 @@ class LocationAuthBackend(ModelBackend):
def authenticate(self, request, byip, **kwargs): def authenticate(self, request, byip, **kwargs):
try: try:
remote_ip = ip_address(request.META.get('REMOTE_ADDR')) remote_ip = ip_address(request.META.get('REMOTE_ADDR'))
lease = IpLeaseModel.objects.filter(ip=str(remote_ip), abon__is_active=True).first()
if lease is None:
user = Abon.objects.filter(ip_address=str(remote_ip), is_active=True).first()
if user is None:
return return
user = Abon.objects.get(ip_addresses=lease)
if self.user_can_authenticate(user): if self.user_can_authenticate(user):
return user return user
except (AddressValueError, Abon.DoesNotExist):
except AddressValueError:
return return
def get_user(self, user_id): def get_user(self, user_id):

9
djing/lib/mixins.py

@ -0,0 +1,9 @@
from django.contrib.auth.mixins import AccessMixin
class OnlyAdminsMixin(AccessMixin):
"""Verify that the current user is admin."""
def dispatch(self, request, *args, **kwargs):
if not request.user.is_admin:
return self.handle_no_permission()
return super().dispatch(request, *args, **kwargs)

1
ip_pool/admin.py

@ -2,4 +2,3 @@ from django.contrib import admin
from ip_pool import models from ip_pool import models
admin.site.register(models.NetworkModel) admin.site.register(models.NetworkModel)
admin.site.register(models.IpLeaseModel)

33
ip_pool/forms.py

@ -1,14 +1,12 @@
from ipaddress import ip_network, ip_address
from ipaddress import ip_network
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
class NetworkForm(forms.ModelForm): class NetworkForm(forms.ModelForm):
def clean_network(self): def clean_network(self):
netw = self.data.get('network') netw = self.data.get('network')
if netw is None: if netw is None:
@ -22,32 +20,3 @@ 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'))

37
ip_pool/migrations/0003_add_leases_history_model.py

@ -0,0 +1,37 @@
# Generated by Django 2.1 on 2018-10-11 11:55
from django.db import migrations, models
import djing.fields
class Migration(migrations.Migration):
dependencies = [
('ip_pool', '0002_change_unique'),
]
operations = [
migrations.CreateModel(
name='LeasesHistory',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('ip', models.GenericIPAddressField(verbose_name='Ip address')),
('lease_time', models.DateTimeField(auto_now_add=True, verbose_name='Lease time')),
('mac_addr', djing.fields.MACAddressField(blank=True, integer=True, null=True, verbose_name='Mac address')),
],
options={
'verbose_name': 'History lease',
'verbose_name_plural': 'Leases history',
'db_table': 'ip_pool_leases_history',
'ordering': ('-lease_time',),
},
),
migrations.RemoveField(
model_name='ipleasemodel',
name='is_active',
),
migrations.RemoveField(
model_name='ipleasemodel',
name='is_dynamic',
),
]

31
ip_pool/models.py

@ -145,7 +145,6 @@ class IpLeaseManager(models.Manager):
ip=ip, ip=ip,
network=net, network=net,
is_dynamic=is_dynamic, is_dynamic=is_dynamic,
is_active=True,
mac_addr=mac mac_addr=mac
) )
except IntegrityError as e: except IntegrityError as e:
@ -159,14 +158,13 @@ class IpLeaseManager(models.Manager):
return self.filter(lease_time__lt=senility) return self.filter(lease_time__lt=senility)
# Deprecated. Remove after migrations squashed
class IpLeaseModel(models.Model): class IpLeaseModel(models.Model):
ip = models.GenericIPAddressField(verbose_name=_('Ip address'), unique=True) ip = models.GenericIPAddressField(verbose_name=_('Ip address'), unique=True)
network = models.ForeignKey(NetworkModel, on_delete=models.CASCADE, network = models.ForeignKey(NetworkModel, on_delete=models.CASCADE,
verbose_name=_('Parent network'), null=True, blank=True) verbose_name=_('Parent network'), null=True, blank=True)
mac_addr = MACAddressField(verbose_name=_('Mac address'), null=True, blank=True) mac_addr = MACAddressField(verbose_name=_('Mac address'), null=True, blank=True)
lease_time = models.DateTimeField(_('Lease time'), auto_now_add=True) lease_time = models.DateTimeField(_('Lease time'), auto_now_add=True)
is_dynamic = models.BooleanField(_('Is dynamic'), default=False)
is_active = models.BooleanField(_('Is active'), default=True)
device_info = models.CharField(null=True, blank=True, default=None, max_length=128) device_info = models.CharField(null=True, blank=True, default=None, max_length=128)
objects = IpLeaseManager() objects = IpLeaseManager()
@ -174,16 +172,6 @@ class IpLeaseModel(models.Model):
def __str__(self): def __str__(self):
return self.ip return self.ip
def free(self):
if self.is_active:
self.is_active = False
self.save(update_fields=('is_active',))
def start(self):
if not self.is_active:
self.is_active = True
self.save(update_fields=('is_active',))
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()
@ -201,7 +189,16 @@ class IpLeaseModel(models.Model):
unique_together = ('ip', 'network', 'mac_addr') unique_together = ('ip', 'network', 'mac_addr')
# class LeasesHistory(models.Model):
# ip = models.GenericIPAddressField(verbose_name=_('Ip address'))
# lease_time = models.DateTimeField(_('Lease time'), auto_now_add=True)
# mac_addr = MACAddressField(_('Mac address'), null=True, blank=True)
class LeasesHistory(models.Model):
ip = models.GenericIPAddressField(verbose_name=_('Ip address'))
lease_time = models.DateTimeField(_('Lease time'), auto_now_add=True)
mac_addr = MACAddressField(_('Mac address'), null=True, blank=True)
def __str__(self):
return self.ip
class Meta:
db_table = 'ip_pool_leases_history'
verbose_name = _('History lease')
verbose_name_plural = _('Leases history')
ordering = '-lease_time',

44
ip_pool/templates/ip_pool/ip_leases_list.html

@ -1,44 +0,0 @@
{% extends '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' net.id %}">{{ net }}</a></li>
<li class="active">{% trans 'Ip leases list' %}</li>
</ol>
{% endblock %}
{% block page-header %}
{% trans 'Ip leases list' %}
{% endblock %}
{% block main %}
<div class="table-responsive">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th class="col-sm-5">{% trans 'Ip' %}</th>
<th class="col-sm-3">{% trans 'Lease time' %}</th>
<th class="col-sm-3">{% trans 'Network' %}</th>
<th class="col-sm-1">{% trans 'Is dynamic' %}</th>
</tr>
</thead>
<tbody>
{% for ip in object_list %}
<tr>
<td>{{ ip.ip }}</td>
<td>{{ ip.lease_time|date:'j:n H:i:s' }}</td>
<td>{{ ip.get_network }}</td>
<td><input type="checkbox" {{ ip.is_dynamic|yesno:'checked,' }}></td>
</tr>
{% empty %}
<tr>
<td colspan="4">{% trans 'You have not any available dedicated ips in this network' %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}

4
ip_pool/templates/ip_pool/net_edit.html

@ -38,10 +38,6 @@
<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>
<a href="{% url 'ip_pool:ip_leases_list' object.pk %}" class="btn btn-default">
<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"> <a href="{% url 'ip_pool:net_groups' object.pk %}" class="btn btn-default">
<span class="glyphicon glyphicon-user"></span> <span class="glyphicon glyphicon-user"></span>
<span class="hidden-xs hidden-sm">{% trans 'Groups available' %}</span> <span class="hidden-xs hidden-sm">{% trans 'Groups available' %}</span>

6
ip_pool/templates/ip_pool/network_list.html

@ -28,7 +28,7 @@
{% with can_ch_net=perms.ip_pool.change_networkmodel can_del_net=perms.ip_poo.delete_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.get_network }}</a></td>
<td>{{ netw.get_network }}</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>{{ netw.get_scope }}</td>
@ -55,10 +55,6 @@
<span class="hidden-xs hidden-sm">{% trans 'Permission denied' %}</span> <span class="hidden-xs hidden-sm">{% trans 'Permission denied' %}</span>
</a> </a>
{% endif %} {% 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>
</a>
</td> </td>
</tr> </tr>
{% empty %} {% empty %}

1
ip_pool/urls.py

@ -7,7 +7,6 @@ app_name = 'ip_pool'
urlpatterns = [ urlpatterns = [
path('', views.NetworksListView.as_view(), name='networks'), path('', views.NetworksListView.as_view(), name='networks'),
path('network_add/', views.NetworkCreateView.as_view(), name='net_add'), path('network_add/', views.NetworkCreateView.as_view(), name='net_add'),
path('<int:net_id>/', views.IpLeasesListView.as_view(), name='ip_leases_list'),
path('<int:net_id>/edit/', views.NetworkUpdateView.as_view(), name='net_edit'), path('<int:net_id>/edit/', views.NetworkUpdateView.as_view(), name='net_edit'),
path('<int:net_id>/del/', views.NetworkDeleteView.as_view(), name='net_delete'), path('<int:net_id>/del/', views.NetworkDeleteView.as_view(), name='net_delete'),
path('<int:net_id>/group_attach/', views.network_in_groups, name='net_groups') path('<int:net_id>/group_attach/', views.network_in_groups, name='net_groups')

17
ip_pool/views.py

@ -57,23 +57,6 @@ class NetworkDeleteView(DeleteView):
return super(NetworkDeleteView, self).delete(request, *args, **kwargs) return super(NetworkDeleteView, self).delete(request, *args, **kwargs)
@method_decorator(login_decs, name='dispatch')
@method_decorator(permission_required('ip_pool.view_ipleasemodel'), name='dispatch')
class IpLeasesListView(OrderedFilteredList):
template_name = 'ip_pool/ip_leases_list.html'
model = models.IpLeaseModel
def get_context_data(self, **kwargs):
net_id = self.kwargs.get('net_id')
context = super().get_context_data(**kwargs)
context['net'] = get_object_or_404(models.NetworkModel, pk=net_id)
return context
def get_queryset(self):
net_id = self.kwargs.get('net_id')
return self.model.objects.filter(network__id=net_id)
@method_decorator(login_decs, name='dispatch') @method_decorator(login_decs, name='dispatch')
@method_decorator(permission_required('ip_pool.add_networkmodel'), name='dispatch') @method_decorator(permission_required('ip_pool.add_networkmodel'), name='dispatch')
class NetworkCreateView(CreateView): class NetworkCreateView(CreateView):

36
nas_app/nas_managers/core.py

@ -108,30 +108,30 @@ class BaseTransmitter(ABC):
def read_users(self) -> VectorAbon: def read_users(self) -> VectorAbon:
pass pass
@abstractmethod
def lease_free(self, user: AbonStruct, lease):
"""
Remove ip lease from allowed to network
:param lease: ip_address for lease
:param user: Subscriber instance
:return:
"""
@abstractmethod
def lease_start(self, user: AbonStruct, lease):
"""
Starts ip lease to allowed to network
:param lease: ip_address for lease
:param user: Subscriber instance
:return:
"""
# @abstractmethod
# def lease_free(self, user: AbonStruct, lease):
# """
# Remove ip lease from allowed to network
# :param lease: ip_address for lease
# :param user: Subscriber instance
# :return:
# """
#
# @abstractmethod
# def lease_start(self, user: AbonStruct, lease):
# """
# Starts ip lease to allowed to network
# :param lease: ip_address for lease
# :param user: Subscriber instance
# :return:
# """
def _diff_users(self, users_from_db: Iterator[Any]) -> Tuple[set, set]: def _diff_users(self, users_from_db: Iterator[Any]) -> Tuple[set, set]:
""" """
:param users_from_db: QuerySet of all subscribers that can have service :param users_from_db: QuerySet of all subscribers that can have service
:return: Tuple of 2 lists that contain list to add users and list to remove users :return: Tuple of 2 lists that contain list to add users and list to remove users
""" """
users_struct_gen = (ab.build_agent_struct(raise_errs=False) for ab in users_from_db if
users_struct_gen = (ab.build_agent_struct() for ab in users_from_db if
ab is not None and ab.is_access()) ab is not None and ab.is_access())
users_struct_set = set(ab for ab in users_struct_gen if ab is not None and ab.tariff is not None) users_struct_set = set(ab for ab in users_struct_gen if ab is not None and ab.tariff is not None)
users_from_nas = set(self.read_users()) users_from_nas = set(self.read_users())

112
nas_app/nas_managers/mod_mikrotik.py

@ -228,17 +228,20 @@ class MikrotikTransmitter(BaseTransmitter, ApiRos, metaclass=type('_ABC_Lazy_mcs
disabled = info.get('=disabled', False) disabled = info.get('=disabled', False)
if disabled is not None: if disabled is not None:
disabled = True if disabled == 'true' else False disabled = True if disabled == 'true' else False
if target is not None and name is not None:
if target and name:
# target may be '192.168.0.3/32,192.168.0.2/32' # target may be '192.168.0.3/32,192.168.0.2/32'
ips = (ip.split('/')[0] for ip in target.split(','))
ip = target.split(',')[0]
if not ip:
return
ip, mask_bits = ip.split('/')
if not ip:
return
a = AbonStruct( a = AbonStruct(
uid=int(name[3:]), uid=int(name[3:]),
ips=ips,
ip=ip,
tariff=t, tariff=t,
is_access=not disabled is_access=not disabled
) )
if len(a.ips) < 1:
return
a.queue_id = info.get('=.id') a.queue_id = info.get('=.id')
return a return a
except ValueError as e: except ValueError as e:
@ -259,12 +262,11 @@ class MikrotikTransmitter(BaseTransmitter, ApiRos, metaclass=type('_ABC_Lazy_mcs
raise TypeError raise TypeError
if user.tariff is None or not isinstance(user.tariff, TariffStruct): if user.tariff is None or not isinstance(user.tariff, TariffStruct):
return return
ips = ','.join(str(i) for i in user.ips)
self._exec_cmd(( self._exec_cmd((
'/queue/simple/add', '/queue/simple/add',
'=name=uid%d' % user.uid, '=name=uid%d' % user.uid,
# FIXME: тут в разных микротиках или =target-addresses или =target # FIXME: тут в разных микротиках или =target-addresses или =target
'=target=%s' % ips,
'=target=%s' % user.ip,
'=max-limit=%.3fM/%.3fM' % (user.tariff.speedOut, user.tariff.speedIn), '=max-limit=%.3fM/%.3fM' % (user.tariff.speedOut, user.tariff.speedIn),
'=queue=Djing_SFQ/Djing_SFQ', '=queue=Djing_SFQ/Djing_SFQ',
'=burst-time=1/1' '=burst-time=1/1'
@ -302,7 +304,7 @@ class MikrotikTransmitter(BaseTransmitter, ApiRos, metaclass=type('_ABC_Lazy_mcs
'=name=uid%d' % user.uid, '=name=uid%d' % user.uid,
'=max-limit=%.3fM/%.3fM' % (user.tariff.speedOut, user.tariff.speedIn), '=max-limit=%.3fM/%.3fM' % (user.tariff.speedOut, user.tariff.speedIn),
# FIXME: тут в разных версиях прошивки микротика или =target-addresses или =target # FIXME: тут в разных версиях прошивки микротика или =target-addresses или =target
'=target=%s' % ','.join(str(i) for i in user.ips),
'=target=%s' % user.ip,
'=queue=Djing_SFQ/Djing_SFQ', '=queue=Djing_SFQ/Djing_SFQ',
'=burst-time=1/1' '=burst-time=1/1'
] ]
@ -377,10 +379,9 @@ class MikrotikTransmitter(BaseTransmitter, ApiRos, metaclass=type('_ABC_Lazy_mcs
self.remove_queue_range(queue_ids) self.remove_queue_range(queue_ids)
for user in users: for user in users:
if isinstance(user, AbonStruct): if isinstance(user, AbonStruct):
for ip in user.ips:
ip_list_entity = self.find_ip(ip, LIST_USERS_ALLOWED)
if ip_list_entity:
self.remove_ip(ip_list_entity.get('=.id'))
ip_list_entity = self.find_ip(user.ip, LIST_USERS_ALLOWED)
if ip_list_entity:
self.remove_ip(ip_list_entity.get('=.id'))
def add_user(self, user: AbonStruct, *args): def add_user(self, user: AbonStruct, *args):
if user.tariff is None: if user.tariff is None:
@ -391,44 +392,40 @@ class MikrotikTransmitter(BaseTransmitter, ApiRos, metaclass=type('_ABC_Lazy_mcs
self.add_queue(user) self.add_queue(user)
except NasFailedResult as e: except NasFailedResult as e:
print('Error:', e) print('Error:', e)
for ip in user.ips:
if not issubclass(ip.__class__, _BaseAddress):
raise TypeError
try:
self.add_ip(LIST_USERS_ALLOWED, ip)
except NasFailedResult as e:
print('Error:', e)
ip = user.ip
if not issubclass(ip.__class__, _BaseAddress):
raise TypeError
try:
self.add_ip(LIST_USERS_ALLOWED, ip)
except NasFailedResult as e:
print('Error:', e)
def remove_user(self, user: AbonStruct): def remove_user(self, user: AbonStruct):
self.remove_queue(user) self.remove_queue(user)
def _finder(ips):
for ip in ips:
r = self.find_ip(ip, LIST_USERS_ALLOWED)
if r: yield r.get('=.id')
firewall_ip_list_ids = _finder(user.ips)
self.remove_ip_range(firewall_ip_list_ids)
r = self.find_ip(user.ip, LIST_USERS_ALLOWED)
ip_id = r.get('=.id')
self.remove_ip(ip_id)
def update_user(self, user: AbonStruct, *args): def update_user(self, user: AbonStruct, *args):
# queue is instance of AbonStruct # queue is instance of AbonStruct
queue = self.find_queue('uid%d' % user.uid) queue = self.find_queue('uid%d' % user.uid)
for ip in user.ips:
if not issubclass(ip.__class__, _BaseAddress):
raise TypeError
nas_ip = self.find_ip(ip, LIST_USERS_ALLOWED)
if user.is_access:
if nas_ip is None:
self.add_ip(LIST_USERS_ALLOWED, ip)
else:
# если не активен - то и обновлять не надо
# но и выключить на всяк случай надо, а то вдруг был включён
if nas_ip:
# и если найден был - то удалим ip из разрешённых
self.remove_ip(nas_ip.get('=.id'))
if queue is not None:
self.remove_queue(user, queue)
queue = None
ip = user.ip
if not issubclass(ip.__class__, _BaseAddress):
raise TypeError
nas_ip = self.find_ip(ip, LIST_USERS_ALLOWED)
if user.is_access:
if nas_ip is None:
self.add_ip(LIST_USERS_ALLOWED, ip)
else:
# если не активен - то и обновлять не надо
# но и выключить на всяк случай надо, а то вдруг был включён
if nas_ip:
# и если найден был - то удалим ip из разрешённых
self.remove_ip(nas_ip.get('=.id'))
if queue is not None:
self.remove_queue(user, queue)
queue = None
# если нет услуги то её не должно быть и в nas # если нет услуги то её не должно быть и в nas
if user.tariff is None: if user.tariff is None:
@ -481,32 +478,3 @@ class MikrotikTransmitter(BaseTransmitter, ApiRos, metaclass=type('_ABC_Lazy_mcs
all_ips = set(ip for ip, mkid in self.read_ips_iter(LIST_USERS_ALLOWED)) all_ips = set(ip for ip, mkid in self.read_ips_iter(LIST_USERS_ALLOWED))
queues = (q for q in self.read_queue_iter() if all_ips.issuperset(q.ips)) queues = (q for q in self.read_queue_iter() if all_ips.issuperset(q.ips))
return queues return queues
def lease_free(self, user: AbonStruct, lease):
queue = self.find_queue('uid%d' % user.uid)
if queue is None:
return
if len(queue.ips) > 1:
if queue is not None:
user.ips = tuple(i for i in user.ips if i != lease)
self.update_queue(user, queue)
ip = self.find_ip(lease, LIST_USERS_ALLOWED)
if ip is not None:
self.remove_ip(ip.get('=.id'))
else:
raise NasFailedResult(_('You cannot disable last session'))
def lease_start(self, user: AbonStruct, lease):
if not issubclass(lease.__class__, _BaseAddress):
lease = ip_address(lease)
if not isinstance(user, AbonStruct):
raise TypeError
ip = self.find_ip(lease, LIST_USERS_ALLOWED)
if ip is None:
self.add_ip(LIST_USERS_ALLOWED, lease)
queue = self.find_queue('uid%d' % user.uid)
user.ips += lease,
if queue is None:
self.add_queue(user)
else:
self.update_queue(user, queue)

30
nas_app/nas_managers/structs.py

@ -1,5 +1,5 @@
from abc import ABCMeta from abc import ABCMeta
from ipaddress import ip_address
from ipaddress import ip_address, _BaseAddress
from typing import Iterable from typing import Iterable
@ -36,38 +36,38 @@ class TariffStruct(BaseStruct):
# Abon from database # Abon from database
class AbonStruct(BaseStruct): class AbonStruct(BaseStruct):
__slots__ = ('uid', '_ips', 'tariff', 'is_access', 'queue_id')
__slots__ = ('uid', '_ip', 'tariff', 'is_access', 'queue_id')
def __init__(self, uid=0, ips=None, tariff=None, is_access=True):
def __init__(self, uid=0, ip=None, tariff=None, is_access=True):
self.uid = int(uid or 0) self.uid = int(uid or 0)
if ips is None:
self._ips = ()
else:
self._ips = tuple(ip_address(ip) for ip in ips)
self._ip = ip
self.tariff = tariff self.tariff = tariff
self.is_access = is_access self.is_access = is_access
self.queue_id = 0 self.queue_id = 0
def get_ips(self):
return self._ips
def get_ip(self):
return self._ip
def set_ips(self, v):
self._ips = set(v)
def set_ip(self, v):
if issubclass(v.__class__, _BaseAddress):
self._ip = v
else:
self._ip = ip_address(v)
ips = property(get_ips, set_ips, doc='Ip addresses')
ip = property(get_ip, set_ip, doc='Ip address')
def __eq__(self, other): def __eq__(self, other):
if not isinstance(other, AbonStruct): if not isinstance(other, AbonStruct):
raise TypeError raise TypeError
r = self.uid == other.uid and self._ips == other._ips
r = self.uid == other.uid and self._ip == other._ip
r = r and self.tariff == other.tariff r = r and self.tariff == other.tariff
return r return r
def __str__(self): def __str__(self):
return "uid=%d, ips=[%s], tariff=%s" % (self.uid, ';'.join(str(i) for i in self._ips), self.tariff or '<No Service>')
return "uid=%d, ip=[%s], tariff=%s" % (self.uid, self._ip, self.tariff or '<No Service>')
def __hash__(self): def __hash__(self):
return hash(hash(self._ips) + hash(self.tariff) if self.tariff is not None else 0)
return hash(hash(self._ip) + hash(self.tariff) if self.tariff is not None else 0)
VectorAbon = Iterable[AbonStruct] VectorAbon = Iterable[AbonStruct]

10
periodic.py

@ -9,7 +9,6 @@ from django.utils import timezone
from django.db import transaction from django.db import transaction
from django.db.models import signals, Count from django.db.models import signals, Count
from abonapp.models import Abon, AbonTariff, abontariff_pre_delete, PeriodicPayForId, AbonLog from abonapp.models import Abon, AbonTariff, abontariff_pre_delete, PeriodicPayForId, AbonLog
from ip_pool.models import IpLeaseModel
from nas_app.nas_managers import NasNetworkError, NasFailedResult from nas_app.nas_managers import NasNetworkError, NasFailedResult
from nas_app.models import NASModel from nas_app.models import NASModel
from djing.lib import LogicError from djing.lib import LogicError
@ -24,10 +23,8 @@ class NasSyncThread(Thread):
try: try:
tm = self.nas.get_nas_manager() tm = self.nas.get_nas_manager()
users = Abon.objects \ users = Abon.objects \
.annotate(ips_count=Count('ip_addresses')) \
.filter(is_active=True, ips_count__gt=0, nas=self.nas) \
.exclude(current_tariff=None) \
.prefetch_related('ip_addresses') \
.filter(is_active=True, nas=self.nas) \
.exclude(current_tariff=None, ip_address=None) \
.iterator() .iterator()
tm.sync_nas(users) tm.sync_nas(users)
except NasNetworkError as er: except NasNetworkError as er:
@ -102,9 +99,6 @@ def main():
for pay in ppays: for pay in ppays:
pay.payment_for_service(now=now) pay.payment_for_service(now=now)
# Remove old inactive ip leases
IpLeaseModel.objects.expired().filter(is_active=False).delete()
# sync subscribers on NAS # sync subscribers on NAS
threads = tuple(NasSyncThread(nas) for nas in NASModel.objects. threads = tuple(NasSyncThread(nas) for nas in NASModel.objects.
annotate(usercount=Count('abon')). annotate(usercount=Count('abon')).

2
searchapp/views.py

@ -21,7 +21,7 @@ def home(request):
if s: if s:
if re.match(IP_ADDR_REGEX, s): if re.match(IP_ADDR_REGEX, s):
abons = Abon.objects.filter(ip_addresses__ip=s)
abons = Abon.objects.filter(ip_address=s)
devices = Device.objects.filter(ip_address=s) devices = Device.objects.filter(ip_address=s)
else: else:
abons = Abon.objects.filter( abons = Abon.objects.filter(

Loading…
Cancel
Save