Browse Source

initial commit

devel
Dmitry Novikov 8 years ago
parent
commit
839ea7ad00
  1. 6
      abonapp/forms.py
  2. 22
      abonapp/migrations/0003_abon_nas.py
  3. 82
      abonapp/models.py
  4. 4
      abonapp/templates/abonapp/addAbon.html
  5. 14
      abonapp/templates/abonapp/editAbon.html
  6. 8
      abonapp/templates/abonapp/group_list.html
  7. 27
      abonapp/templates/abonapp/modal_attach_nas.html
  8. 5
      abonapp/urls.py
  9. 55
      abonapp/views.py
  10. 10
      agent/__init__.py
  11. 4
      agent/commands/dhcp.py
  12. 11
      agent/netflow/mysql_install.sql
  13. 13
      agent/settings.py.example
  14. 4
      clientsideapp/views.py
  15. 2
      djing/local_settings.py.template
  16. 1
      djing/settings.py
  17. 3
      djing/urls.py
  18. 20
      docs/dev.md
  19. 3
      group_app/models.py
  20. 0
      nas_app/__init__.py
  21. 3
      nas_app/admin.py
  22. 5
      nas_app/apps.py
  23. 28
      nas_app/forms.py
  24. 36
      nas_app/migrations/0001_initial.py
  25. 0
      nas_app/migrations/__init__.py
  26. 63
      nas_app/models.py
  27. 9
      nas_app/nas_managers/__init__.py
  28. 22
      nas_app/nas_managers/core.py
  29. 33
      nas_app/nas_managers/mod_mikrotik.py
  30. 0
      nas_app/nas_managers/structs.py
  31. 31
      nas_app/templates/nas_app/nasmodel_add.html
  32. 17
      nas_app/templates/nas_app/nasmodel_confirm_delete.html
  33. 60
      nas_app/templates/nas_app/nasmodel_list.html
  34. 42
      nas_app/templates/nas_app/nasmodel_update.html
  35. 200
      nas_app/tests.py
  36. 13
      nas_app/urls.py
  37. 66
      nas_app/views.py
  38. 2
      periodic.py
  39. 23
      statistics/migrations/0003_auto_20180814_1921.py
  40. 7
      templates/base.html

6
abonapp/forms.py

@ -3,6 +3,8 @@ from django import forms
from django.contrib.auth.hashers import make_password
from random import choice
from string import digits, ascii_lowercase
from nas_app.models import NASModel
from . import models
from django.conf import settings
@ -41,6 +43,8 @@ class AbonForm(forms.ModelForm):
abon_group_queryset = None
if abon_group_queryset is not None:
self.fields['street'].queryset = abon_group_queryset
if not instance:
self.initial['nas'] = NASModel.objects.filter(default=True).first()
username = forms.CharField(max_length=127, required=False, initial=_generate_random_username,
widget=forms.TextInput(attrs={
@ -55,7 +59,7 @@ class AbonForm(forms.ModelForm):
class Meta:
model = models.Abon
fields = ('username', 'telephone', 'fio', 'group', 'description', 'street', 'house', 'is_active')
fields = ('username', 'telephone', 'fio', 'group', 'description', 'street', 'house', 'is_active', 'nas')
widgets = {
'fio': forms.TextInput(attrs={
'placeholder': _('fio'),

22
abonapp/migrations/0003_abon_nas.py

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-08-16 18:40
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('nas_app', '0001_initial'),
('abonapp', '0002_auto_20180808_1448'),
]
operations = [
migrations.AddField(
model_name='abon',
name='nas',
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='nas_app.NASModel', verbose_name='Network access server'),
),
]

82
abonapp/models.py

@ -13,7 +13,7 @@ from django.utils import timezone
from django.utils.translation import ugettext_lazy as _, gettext
from accounts_app.models import UserProfile, MyUserManager, BaseAccount
from agent import Transmitter, AbonStruct, TariffStruct, NasFailedResult, NasNetworkError
from nas_app.nas_managers import AbonStruct, TariffStruct, NasFailedResult, NasNetworkError
from group_app.models import Group
from djing.lib import LogicError
from ip_pool.models import IpLeaseModel, NetworkModel
@ -33,7 +33,7 @@ class AbonLog(models.Model):
permissions = (
('can_view_abonlog', _('Can view subscriber logs')),
)
ordering = ('-date',)
ordering = '-date',
def __str__(self):
return self.comment
@ -81,7 +81,7 @@ class AbonStreet(models.Model):
db_table = 'abon_street'
verbose_name = _('Street')
verbose_name_plural = _('Streets')
ordering = ('name',)
ordering = 'name',
class AbonManager(MyUserManager):
@ -94,13 +94,13 @@ class Abon(BaseAccount):
group = models.ForeignKey(Group, models.SET_NULL, blank=True, null=True, verbose_name=_('User group'))
ballance = models.FloatField(default=0.0)
ip_addresses = models.ManyToManyField(IpLeaseModel, verbose_name=_('Ip addresses'))
# ip_address = models.GenericIPAddressField(blank=True, null=True, verbose_name=_('Ip Address'))
description = models.TextField(_('Comment'), null=True, blank=True)
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)
device = models.ForeignKey('devapp.Device', null=True, blank=True, on_delete=models.SET_NULL)
dev_port = models.ForeignKey('devapp.Port', null=True, blank=True, on_delete=models.SET_NULL)
is_dynamic_ip = models.BooleanField(default=False)
nas = models.ForeignKey('nas_app.NASModel', null=True, blank=True, on_delete=models.SET_NULL, verbose_name=_('Network access server'), default=None)
MARKER_FLAGS = (
('icon_donkey', _('Donkey')),
@ -227,14 +227,51 @@ class Abon(BaseAccount):
return AbonStruct(self.pk, abon_addresses, agent_trf, self.is_access())
raise LogicError(_('You have not any active leases'))
def sync_with_nas(self, created: bool) -> Optional[Exception]:
def nas_sync_self(self) -> Optional[Exception]:
"""
Synchronize user with gateway(NAS)
:return:
"""
if self.nas is None:
raise LogicError(_('NAS required'))
try:
agent_abon = self.build_agent_struct()
tm = Transmitter()
if created:
tm.add_user(agent_abon)
else:
tm.update_user(agent_abon)
mngr = self.nas.get_nas_manager()
mngr.update_user(agent_abon)
except (NasFailedResult, NasNetworkError, ConnectionResetError) as e:
print('ERROR:', e)
return e
except LogicError:
pass
def nas_add_self(self):
"""
Will add this user to network access server
:return:
"""
if self.nas is None:
raise LogicError(_('NAS required'))
try:
agent_abon = self.build_agent_struct()
mngr = self.nas.get_nas_manager()
mngr.add_user(agent_abon)
except (NasFailedResult, NasNetworkError, ConnectionResetError) as e:
print('ERROR:', e)
return e
except LogicError:
pass
def nas_remove_self(self):
"""
Will remove this user to network access server
:return:
"""
if self.nas is None:
raise LogicError(_('NAS required'))
try:
agent_abon = self.build_agent_struct()
mngr = self.nas.get_nas_manager()
mngr.remove_user(agent_abon)
except (NasFailedResult, NasNetworkError, ConnectionResetError) as e:
print('ERROR:', e)
return e
@ -422,13 +459,11 @@ class PeriodicPayForId(models.Model):
@receiver(post_delete, sender=Abon)
def abon_del_signal(sender, **kwargs):
abon = kwargs["instance"]
abon = kwargs.get("instance")
if abon is None:
raise ValueError('Instance does not passed to a signal')
try:
ab = abon.build_agent_struct()
if ab is None:
return True
tm = Transmitter()
tm.remove_user(ab)
abon.nas_remove_self()
except (NasFailedResult, NasNetworkError, LogicError):
return True
@ -445,16 +480,13 @@ def abon_tariff_post_init(sender, **kwargs):
@receiver(pre_delete, sender=AbonTariff)
def abontariff_pre_delete(sender, **kwargs):
abon_tariff = kwargs["instance"]
abon_tariff = kwargs.get("instance")
if abon_tariff is None:
raise ValueError('Instance does not passed to a signal')
try:
abon = Abon.objects.get(current_tariff=abon_tariff)
ab = abon.build_agent_struct()
if ab is None:
abon.nas_remove_self()
except (NasFailedResult, NasNetworkError, LogicError):
return True
tm = Transmitter()
tm.remove_user(ab)
except Abon.DoesNotExist:
print('ERROR: Abon.DoesNotExist')
except (NasFailedResult, NasNetworkError, ConnectionResetError) as e:
print('NetErr:', e)
return True
print('Error: abontariff_pre_delete - user not found')

4
abonapp/templates/abonapp/addAbon.html

@ -54,6 +54,10 @@
});
</script>
{# Nas server #}
{% bootstrap_icon 'globe' as ic %}
{% bootstrap_field form.nas addon_before=ic %}
<div class="btn-group">
<button type="submit" class="btn btn-sm btn-primary">
<span class="glyphicon glyphicon-save"></span> {% trans 'Save' %}

14
abonapp/templates/abonapp/editAbon.html

@ -37,15 +37,6 @@
{% bootstrap_field form.telephone form_group_class='form-group-sm' addon_after_class='input-group-btn' addon_after=bt %}
{% endwith %}
{# Ip address field #}
{# {% trans 'Reset ip' as tx %}#}
{# {% url 'abonapp:reset_ip' group.pk abon.username as url %}#}
{# {% if abon.is_dynamic_ip %}#}
{# {% bootstrap_button '' button_type='link' icon='refresh' button_class='btn-default btn-cmd' id='iprefreshbtn' href=url size='sm' title=tx as bt %}#}
{# {% endif %} #}
{# {% bootstrap_field form.ip_address form_group_class='form-group-sm' addon_after_class='input-group-btn' addon_after=bt %}#}
{% bootstrap_field form.street form_group_class='form-group-sm' %}
{% bootstrap_field form.house form_group_class='form-group-sm' %}
{% bootstrap_field form.is_active form_group_class='form-group-sm' %}
@ -70,6 +61,9 @@
});
</script>
{% if perms.nas_app.change_nasmodel %}
{% bootstrap_field form.nas form_group_class='form-group-sm' %}
{% endif %}
{% bootstrap_field form.description form_group_class='form-group-sm' %}
<div class="form-group-sm">
@ -201,7 +195,7 @@
{% endif %}
{% if can_ping %}
<a href="{% url 'abonapp:ping' %}" class="btn btn-default btn-cmd" title="Ping" data-param="{{ lease.ip }}">
<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 %}

8
abonapp/templates/abonapp/group_list.html

@ -37,11 +37,13 @@
<td>{{ gr.pk }}</td>
<td><a href="{% url 'abonapp:people_list' gr.pk %}">{{ gr.title }}</a></td>
<td class="hidden-xs">{{ gr.usercount }}</td>
<td class="btn-group">
<a href="{% url 'abonapp:ch_group_tariff' gr.pk %}" class="btn btn-sm btn-default"
title="{% trans 'User groups' %}">
<td class="btn-group btn-group-sm">
<a href="{% url 'abonapp:ch_group_tariff' gr.pk %}" class="btn btn-default" title="{% trans 'User groups' %}">
<span class="glyphicon glyphicon-cog"></span>
</a>
<a href="{% url 'abonapp:attach_nas' gr.pk %}" class="btn btn-default btn-modal" title="{% trans 'NAS' %}">
<span class="glyphicon glyphicon-globe"></span>
</a>
</td>
</tr>
{% empty %}

27
abonapp/templates/abonapp/modal_attach_nas.html

@ -0,0 +1,27 @@
{% load i18n %}
<form role="form" action="{% url 'abonapp:attach_nas' gid %}" 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-globe"></span>{% trans 'Attach gateway' %}</h4>
</div>
<div class="modal-body">
<div class="form-group-sm">
<label class="control-label" for="id_gateway">{% trans 'Select gateway' %}</label>
<select name="gateway" class="form-control" id="id_gateway">
{% for nas in nas_list %}
<option value="{{ nas.pk }}">{{ nas.title }}</option>
{% empty %}
<option value="0">{% trans 'Gateways does not exist' %}</option>
{% endfor %}
</select>
</div>
<div class="btn-group">
<button type="submit" class="btn btn-primary">
<span class="glyphicon glyphicon-save"></span> {% trans 'Attach' %}
</button>
</div>
</div>
</form>

5
abonapp/urls.py

@ -31,7 +31,8 @@ subscriber_patterns = [
url(r'^periodic_pay$', views.add_edit_periodic_pay, name='add_periodic_pay'),
url(r'^periodic_pay(?P<periodic_pay_id>\d+)/$', views.add_edit_periodic_pay, name='add_periodic_pay'),
url(r'^periodic_pay(?P<periodic_pay_id>\d+)/del/$', views.del_periodic_pay, name='del_periodic_pay'),
url(r'^lease/add/$', views.lease_add, name='lease_add')
url(r'^lease/add/$', views.lease_add, name='lease_add'),
url(r'^ping$', views.abon_ping, name='ping')
]
group_patterns = [
@ -44,6 +45,7 @@ group_patterns = [
url(r'^street/edit', views.street_edit, name='street_edit'),
url(r'^street/(?P<sid>\d+)/delete$', views.street_del, name='street_del'),
url(r'^active_networks/$', views.active_nets, name='active_nets'),
url(r'^attach_nas/$', views.attach_nas, name='attach_nas'),
url(r'^(?P<uname>\w{1,127})/', include(subscriber_patterns))
]
@ -54,7 +56,6 @@ urlpatterns = [
url(r'^log$', views.LogListView.as_view(), name='log'),
url(r'^pay$', views.terminal_pay, name='terminal_pay'),
url(r'^debtors$', views.DebtorsListView.as_view(), name='debtors'),
url(r'^ping$', views.abon_ping, name='ping'),
url(r'^contacts/vcards/$', views.vcards, name='vcards'),
# Api's

55
abonapp/views.py

@ -18,8 +18,9 @@ from djing.lib import DuplicateEntry
from jsonview.decorators import json_view
from agent.commands.dhcp import dhcp_commit, dhcp_expiry, dhcp_release
from nas_app.models import NASModel
from tariff_app.models import Tariff
from agent import NasFailedResult, Transmitter, NasNetworkError
from nas_app.nas_managers import NasFailedResult, NasNetworkError
from . import forms
from . import models
from devapp.models import Device, Port as DevPort
@ -126,7 +127,6 @@ class AbonCreateView(CreateView):
assign_perm("abonapp.can_buy_tariff", me, abon)
assign_perm("abonapp.can_view_passport", me, abon)
assign_perm('abonapp.can_add_ballance', me, abon)
# abon.sync_with_nas(created=True)
messages.success(self.request, _('create abon success msg'))
self.abon = abon
return super(AbonCreateView, self).form_valid(form)
@ -304,7 +304,7 @@ class AbonHomeUpdateView(UpdateView):
def form_valid(self, form):
r = super(AbonHomeUpdateView, self).form_valid(form)
abon = self.object
res = abon.sync_with_nas(created=False)
res = abon.nas_sync_self()
if isinstance(res, Exception):
messages.warning(self.request, res)
messages.success(self.request, _('edit abon success msg'))
@ -412,7 +412,7 @@ def pick_tariff(request, gid, uname):
else:
deadline = datetime.strptime(deadline, '%Y-%m-%d %H:%M:%S')
abon.pick_tariff(trf, request.user, deadline=deadline, comment=log_comment)
r = abon.sync_with_nas(created=False)
r = abon.nas_sync_self()
if r is None:
messages.success(request, _('Tariff has been picked'))
else:
@ -652,15 +652,22 @@ def charts(request, gid, uname):
@login_required
@permission_required('abonapp.can_ping')
@json_view
def abon_ping(request):
def abon_ping(request, gid, uname):
ip = request.GET.get('cmd_param')
status = False
text = '<span class="glyphicon glyphicon-exclamation-sign"></span> %s' % _('no ping')
abon = get_object_or_404(models.Abon, username=uname)
try:
if ip is None:
raise lib.LogicError(_('Ip not passed'))
tm = Transmitter()
ping_result = tm.ping(ip)
if abon.nas is None:
return {
'status': 1,
'dat': '<span class="glyphicon glyphicon-exclamation-sign"></span> %s' % _('NAS required')
}
mngr = abon.nas.get_nas_manager()
ping_result = mngr.ping(ip)
if ping_result is None:
if ping(ip, 10):
status = True
@ -1089,8 +1096,11 @@ class EditSibscriberMarkers(UpdateView):
@lib.decorators.only_admins
def user_session_toggle(request, gid, uname, lease_id, action=None):
abon = get_object_or_404(models.Abon, username=uname)
if abon.nas is None:
messages.error(request, _('NAS required'))
return redirect('abonapp:abon_home', gid, uname)
lease = abon.ip_addresses.get(pk=lease_id)
tm = Transmitter()
tm = abon.nas.get_nas_manager()
try:
if action == 'free':
try:
@ -1105,6 +1115,8 @@ def user_session_toggle(request, gid, uname, lease_id, action=None):
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)
@ -1127,7 +1139,10 @@ def lease_add(request, gid, uname):
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)
tm = Transmitter()
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
@ -1153,6 +1168,28 @@ def lease_add(request, gid, uname):
})
@login_required
@lib.decorators.only_admins
def attach_nas(request, gid):
if request.method == 'POST':
gateway_id = lib.safe_int(request.POST.get('gateway'))
if gateway_id:
nas = get_object_or_404(NASModel, pk=gateway_id)
abons = models.Abon.objects.filter(group__id=gid)
if abons.exists():
abons.update(nas=nas)
messages.success(request, _('Network access server for users in this group, has been updated'))
return redirect('abonapp:group_list')
else:
messages.warning(request, _('Users not found'))
else:
messages.error(request, _('You must select gateway'))
return render(request, 'abonapp/modal_attach_nas.html', {
'gid': gid,
'nas_list': NASModel.objects.all().iterator()
})
# API's
@login_required
@lib.decorators.only_admins

10
agent/__init__.py

@ -1,10 +0,0 @@
# -*- coding: utf-8 -*-
from .mod_mikrotik import MikrotikTransmitter
from .core import NasFailedResult, NasNetworkError
from .structs import TariffStruct, AbonStruct
# Transmitter мы будем импортировать в других местах
# Тут надо указать какой у нас будет NAS
# т.е. какой класс будет управлять доступом в интернет
# TODO: Transmitter can be lazy init
Transmitter = MikrotikTransmitter

4
agent/commands/dhcp.py

@ -24,7 +24,7 @@ def dhcp_commit(client_ip: str, client_mac: str, switch_mac: str, switch_port: i
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.sync_with_nas(created=False)
abon.nas_sync_self()
else:
return 'User %s is not access to service' % abon.username
else:
@ -50,7 +50,7 @@ def dhcp_expiry(client_ip: str) -> Optional[str]:
abon = Abon.objects.filter(ip_addresses=lease).first()
if abon is None:
return "Subscriber with ip %s does not exist" % client_ip
abon.sync_with_nas(created=False)
abon.nas_sync_self()
except IpLeaseModel.DoesNotExist:
pass

11
agent/netflow/mysql_install.sql

@ -1,11 +0,0 @@
DROP TABLE `flowcache`;
CREATE TABLE `flowcache` (
`last_time` INT(10) UNSIGNED NOT NULL,
`abon_id` INT(11) DEFAULT NULL UNIQUE,
`octets` INT(10) UNSIGNED NOT NULL,
`packets` INT(10) UNSIGNED NOT NULL,
KEY `flowcache_abon_id_91e1085d` (`abon_id`)
)
ENGINE = MEMORY
DEFAULT CHARSET = utf8;

13
agent/settings.py.example

@ -1,13 +0,0 @@
# Setting NAS module in __init__.py
# Certificates
#CERTFILE = "/etc/ssl/server.crt"
#KEYFILE = "/etc/ssl/server.key"
#IS_USE_SSL = False
# information for access on NAS server
NAS_IP = '<NAS IP>'
NAS_LOGIN = 'admin'
NAS_PASSW = '<PASSWORD>'
NAS_PORT = 8728

4
clientsideapp/views.py

@ -9,7 +9,7 @@ from abonapp.models import AbonLog, InvoiceForPayment, Abon
from tariff_app.models import Tariff
from taskapp.models import Task
from djing.lib import LogicError
from agent import NasFailedResult, NasNetworkError
from nas_app.nas_managers import NasFailedResult, NasNetworkError
@login_required
@ -53,7 +53,7 @@ def buy_service(request, srv_id):
if request.method == 'POST':
abon.pick_tariff(service, None, _("Buy the service via user side, service '%s'")
% service)
abon.sync_with_nas(created=False)
abon.nas_sync_self()
messages.success(request, _("The service '%s' wan successfully activated") % service.title)
else:
return render_to_text('clientsideapp/modal_service_buy.html', {

2
djing/local_settings.py.template

@ -16,7 +16,7 @@ SECRET_KEY = '!!!!!!!!!!!!!!!!!!!!!!!!YOUR SECRET KEY!!!!!!!!!!!!!!!!!!!!!!!!'
DATABASES = {
'default': {
'OPTIONS': {
'init_command': "SET sql_mode='STRICT_TRANS_TABLES'",
'init_command': "SET sql_mode='STRICT_TRANS_TABLES', default_storage_engine=INNODB",
'isolation_level': 'read uncommitted'
},
#'ENGINE': 'django.db.backends.sqlite3',

1
djing/settings.py

@ -40,6 +40,7 @@ INSTALLED_APPS = [
'django.contrib.staticfiles',
'ip_pool',
'accounts_app',
'nas_app',
'abonapp',
'tariff_app',
'searchapp',

3
djing/urls.py

@ -17,7 +17,8 @@ urlpatterns = [
url(r'^msg/', include('msg_app.urls', namespace='msg_app')),
url(r'^dialing/', include('dialing_app.urls', namespace='dialapp')),
url(r'^groups/', include('group_app.urls', namespace='group_app')),
url(r'^ip_pool/', include('ip_pool.urls', namespace='ip_pool'))
url(r'^ip_pool/', include('ip_pool.urls', namespace='ip_pool')),
url(r'^nas/', include('nas_app.urls', namespace='nas_app'))
# Switch language
#url(r'^i18n/', include('django.conf.urls.i18n')),

20
docs/dev.md

@ -150,7 +150,7 @@ def terminal_pay(request):
Сейчас биллинг работает с Mikrotik в роли устройства для доступа абонентов в интернет.
Как можно реализовать такой-же для вашего роутера, например на GNU/Linux.
Создадим файл *agent/mod_linux.py* и реализуем потомка для интерфейса *BaseTransmitter*.
Создадим файл *nas_app/nas_managers/mod_linux.py* и реализуем потомка для интерфейса *BaseTransmitter*.
Методы вашего класса будут вызываться биллингом для взаимодействия с сервером доступа абонентов в интернет(NAS).
```python
@ -229,7 +229,7 @@ class LinuxTransmitter(BaseTransmitter):
"""
```
Для того чтоб биллинг знал о вашем классе надо указать его в *agent/\_\_init\_\_.py*.
Для того чтоб биллинг знал о вашем классе надо указать его в *nas_app/nas_managers/\_\_init\_\_.py*.
Замените
>from .mod_mikrotik import MikrotikTransmitter
@ -249,27 +249,13 @@ from .structs import TariffStruct, AbonStruct
Transmitter = LinuxTransmitter
```
Для примера, как вы наверное уже догадались, можно посмотреть реализацию для Mikrotik в файле *agent/mod_mikrotik.py*
Для примера, как вы наверное уже догадались, можно посмотреть реализацию для Mikrotik в файле *nas_app/nas_managers/mod_mikrotik.py*
Чтобы выводить в биллинге различные сообщения об ошибках есть 2 типа исключений: *NasFailedResult* и *NasNetworkError*.
NasNetworkError, как понятно из названия, вызывается при проблемах в сети. А NasFailedResult при ошибочных кодах возврата из модуля на сервере NAS.
Биллинг прослушивает эти исключения при выполнении, и при возбуждении этих исключений отображает текст ошибки на экране пользователя.
При переопределении базового класса пожалуйста не забывайте вызвать базовый метод чтоб отработали декораторы методов
интерфейса, этот декоратор проверяет тип входных данных.
Динамическая типизация python иногда подкладывает свинью в том смысле что можно передать не то что вы хотели бы передать,
потому типы лучше проконтролировать, и тогда интерпретатор станет вашим другом помошником :)
Когда я прошу вызвать базовый метод, я имею ввиду это:
```python
...
def add_user_range(self, user_list):
super(LinuxTransmitter, self).add_user_range(user_list)
# ваш код
...
```
Кстати, не все методы обязательно реализовывать, некоторые из них зарезервированы на будущие цели, в комментариях к их
прототипам в интерфейсе *BaseTransmitter* это сказано. Поэтому просто переопределите эти зарезервированные методы как
пустые, например метод *add_tariff_range* нигде в биллинге пока не вызывается. так что можно определить его пустым.

3
group_app/models.py

@ -8,8 +8,7 @@ class Group(models.Model):
code = models.CharField(_('Tech code'), blank=True, max_length=12)
def get_absolute_url(self):
url = resolve_url('group_app:edit', self.pk)
return url
return resolve_url('group_app:edit', self.pk)
class Meta:
db_table = 'groups'

0
nas_app/__init__.py

3
nas_app/admin.py

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

5
nas_app/apps.py

@ -0,0 +1,5 @@
from django.apps import AppConfig
class NasAppConfig(AppConfig):
name = 'nas_app'

28
nas_app/forms.py

@ -0,0 +1,28 @@
from django import forms
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
from nas_app.models import NASModel
from djing import IP_ADDR_REGEX
class NasForm(forms.ModelForm):
def clean_default(self):
cd = self.cleaned_data
default = cd.get('default')
if default:
try:
NASModel.objects.get(default=True)
raise ValidationError(message=_('Can be only one default gateway'), code='unique')
except NASModel.DoesNotExist:
pass
return default
class Meta:
model = NASModel
fields = '__all__'
widgets = {
'ip_address': forms.TextInput(attrs={
'pattern': IP_ADDR_REGEX
})
}

36
nas_app/migrations/0001_initial.py

@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-08-17 17:18
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='NASModel',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=127, unique=True, verbose_name='Title')),
('ip_address', models.GenericIPAddressField(unique=True, verbose_name='Ip address')),
('ip_port', models.PositiveSmallIntegerField(verbose_name='Port')),
('auth_login', models.CharField(max_length=64, verbose_name='Login')),
('auth_passw', models.CharField(max_length=127, verbose_name='Password')),
('nas_type', models.CharField(choices=[('mktk', 'Mikrotik NAS')], default='mktk', max_length=4, verbose_name='Type')),
('default', models.BooleanField(default=False, verbose_name='Is default')),
],
options={
'verbose_name': 'Network access server. Gateway',
'verbose_name_plural': 'Network access servers. Gateways',
'db_table': 'nas',
'ordering': ('ip_address',),
'permissions': (('can_view_nas', 'Can view NAS'),),
},
),
]

0
nas_app/migrations/__init__.py

63
nas_app/models.py

@ -0,0 +1,63 @@
from django.contrib.messages import MessageFailure
from django.db.models.signals import pre_delete
from django.dispatch import receiver
from django.shortcuts import resolve_url
from django.utils.translation import gettext_lazy as _
from django.db import models
from djing.lib import MyChoicesAdapter
from nas_app.nas_managers import NAS_TYPES
class NASModel(models.Model):
title = models.CharField(_('Title'), max_length=127, unique=True)
ip_address = models.GenericIPAddressField(_('Ip address'), unique=True)
ip_port = models.PositiveSmallIntegerField(_('Port'))
auth_login = models.CharField(_('Login'), max_length=64)
auth_passw = models.CharField(_('Password'), max_length=127)
nas_type = models.CharField(_('Type'), max_length=4, choices=MyChoicesAdapter(NAS_TYPES), default=NAS_TYPES[0][0])
default = models.BooleanField(_('Is default'), default=False)
def get_nas_manager_klass(self):
try:
return next(klass for code, klass in NAS_TYPES if code == self.nas_type)
except StopIteration:
raise TypeError(_('One of nas types implementation is not found'))
def get_nas_manager(self):
klass = self.get_nas_manager_klass()
if hasattr(self, '_nas_mngr'):
o = getattr(self, '_nas_mngr')
setattr(self, '_nas_mngr', o)
else:
o = klass(
login=self.auth_login,
password=self.auth_passw,
ip=self.ip_address,
port=int(self.ip_port)
)
setattr(self, '_nas_mngr', o)
return o
def get_absolute_url(self):
return resolve_url('nas_app:edit', self.pk)
def __str__(self):
return self.title
class Meta:
db_table = 'nas'
verbose_name = _('Network access server. Gateway')
verbose_name_plural = _('Network access servers. Gateways')
ordering = 'ip_address',
permissions = (
('can_view_nas', _('Can view NAS')),
)
@receiver(pre_delete, sender=NASModel)
def nas_pre_delete(sender, **kwargs):
nas = kwargs.get("instance")
# check if this nas is default.
# You cannot remove default server
if nas.default:
raise MessageFailure(_('You cannot remove default server'))

9
nas_app/nas_managers/__init__.py

@ -0,0 +1,9 @@
from nas_app.nas_managers.mod_mikrotik import MikrotikTransmitter
from nas_app.nas_managers.core import NasNetworkError, NasFailedResult
from nas_app.nas_managers.structs import TariffStruct, AbonStruct
# Указываем какие реализации NAS у нас есть, это будет использоваться в
# web интерфейсе
NAS_TYPES = (
('mktk', MikrotikTransmitter),
)

22
agent/core.py → nas_app/nas_managers/core.py

@ -1,7 +1,7 @@
from abc import ABC, abstractmethod
from abc import ABC, abstractmethod, abstractproperty
from typing import Iterator, Any, Tuple, Optional
from .structs import AbonStruct, TariffStruct, VectorAbon, VectorTariff
from djing import ping
from nas_app.nas_managers.structs import AbonStruct, TariffStruct, VectorAbon, VectorTariff
# Raised if NAS has returned failed result
@ -16,6 +16,22 @@ class NasNetworkError(Exception):
# Communicate with NAS
class BaseTransmitter(ABC):
@abstractproperty
def description(self):
"""
:return: Returnd a description of nas implementation
"""
def __init__(self, login: str, password: str, ip: str, port: int, *args, **kwargs):
if not ping(ip):
raise NasNetworkError('NAS %(ip_addr)s does not pinged' % {
'ip_addr': ip
})
@classmethod
def get_description(cls):
return cls.description
@abstractmethod
def add_user_range(self, user_list: VectorAbon):
"""add subscribers list to NAS"""

33
agent/mod_mikrotik.py → nas_app/nas_managers/mod_mikrotik.py

@ -1,6 +1,6 @@
import binascii
import re
import socket
import binascii
from abc import ABCMeta
from hashlib import md5
from ipaddress import _BaseAddress, ip_address
@ -8,12 +8,9 @@ from typing import Iterable, Optional, Tuple, Generator, Dict
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from djing.lib.decorators import LazyInitMetaclass
from .structs import TariffStruct, AbonStruct, VectorAbon, VectorTariff
from . import settings as local_settings
from djing import ping
from agent.core import BaseTransmitter, NasNetworkError, NasFailedResult
from nas_app.nas_managers.core import BaseTransmitter, NasNetworkError, NasFailedResult
from nas_app.nas_managers.structs import TariffStruct, AbonStruct, VectorAbon, VectorTariff
DEBUG = getattr(settings, 'DEBUG', False)
@ -29,8 +26,6 @@ class ApiRos(object):
def __init__(self, ip: str, port: int):
if self.sk is None:
sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
if port is None:
port = local_settings.NAS_PORT
sk.connect((ip, port or 8728))
self.sk = sk
@ -168,20 +163,16 @@ class ApiRos(object):
class MikrotikTransmitter(BaseTransmitter, ApiRos, metaclass=type('_ABC_Lazy_mcs', (ABCMeta, LazyInitMetaclass), {})):
def __init__(self, login=None, password=None, ip=None, port=None):
ip = ip or getattr(local_settings, 'NAS_IP')
if ip is None or ip == '<NAS IP>':
raise NasNetworkError('Ip address of NAS does not specified')
if not ping(ip):
raise NasNetworkError('NAS %(ip_addr)s does not pinged' % {
'ip_addr': ip
})
description = _('Mikrotik NAS')
def __init__(self, login: str, password: str, ip: str, port: int, *args, **kwargs):
try:
super(MikrotikTransmitter, self).__init__(ip, port)
self.login(
login or getattr(local_settings, 'NAS_LOGIN'),
password or getattr(local_settings, 'NAS_PASSW')
ApiRos.__init__(self, ip, port)
MikrotikTransmitter.__init__(self,
login=login, password=password, ip=ip,
port=port, *args, **kwargs
)
self.login(username=login, pwd=password)
except ConnectionRefusedError:
raise NasNetworkError('Connection to %s is Refused' % ip)
@ -405,10 +396,12 @@ class MikrotikTransmitter(BaseTransmitter, ApiRos, metaclass=type('_ABC_Lazy_mcs
def remove_user(self, user: AbonStruct):
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)

0
agent/structs.py → nas_app/nas_managers/structs.py

31
nas_app/templates/nas_app/nasmodel_add.html

@ -0,0 +1,31 @@
{% extends request.is_ajax|yesno:'bajax.html,base.html' %}
{% load i18n %}
{% load bootstrap3 %}
{% block breadcrumb %}
<ol class="breadcrumb">
<li><span class="glyphicon glyphicon-home"></span></li>
<li><a href="{% url 'nas_app:home' %}">{% trans 'Network access servers' %}</a></li>
<li class="active">{% trans 'Add gateway' %}</li>
</ol>
{% endblock %}
{% block page-header %}
{% trans 'Add new gateway' %}
{% endblock %}
{% block main %}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{% trans 'Add gateway' %}</h3>
</div>
<div class="panel-body">
<form action="{% url 'nas_app:add' %}" method="post">{% csrf_token %}
{% bootstrap_form form %}
<button type="submit" class="btn btn-primary btn-sm">
<span class="glyphicon glyphicon-save"></span> {% trans 'Save' %}
</button>
</form>
</div>
</div>
{% endblock %}

17
nas_app/templates/nas_app/nasmodel_confirm_delete.html

@ -0,0 +1,17 @@
{% extends 'base_delete_modal.html' %}
{% load i18n %}
{% block modal_form_url %}
{% url 'nas_app:del' object.pk %}
{% endblock %}
{% block modal_form_title %}
{% trans 'Remove NAS' %}
{% endblock %}
{% block modal_form_text %}
{% blocktrans trimmed %}
<h4>If you remove this server, then all users than has been
attached to them will lost parent NAS server.</h4>
{% endblocktrans %}
{% endblock %}

60
nas_app/templates/nas_app/nasmodel_list.html

@ -0,0 +1,60 @@
{% 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 class="active">{% trans 'Network access servers' %}</li>
</ol>
{% endblock %}
{% block page-header %}
{% trans 'Gateways' %}
{% endblock %}
{% block main %}
<div class="row">
{% for nas in object_list %}
<div class="col-sm-6">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">dasdasd</h3>
</div>
<div class="panel-body">
<dl class="dl-horizontal">
<dt>{% trans 'Title' %}</dt> <dd>{{ nas.title }}</dd>
<dt>{% trans 'Ip address' %}</dt> <dd>{{ nas.ip_address }}</dd>
<dt>{% trans 'Port' %}</dt> <dd>{{ nas.ip_port }}</dd>
<dt>{% trans 'Auth login' %}</dt> <dd>{{ nas.auth_login }}</dd>
<dt>{% trans 'Auth password' %}</dt> <dd>{{ nas.auth_passw }}</dd>
<dt>{% trans 'NAS type' %}</dt> <dd>{{ nas.get_nas_type_display }}</dd>
<dt>{% trans 'Is default' %}</dt>
<dd>
<input type="checkbox" {{ nas.default|yesno:'checked,' }}>
</dd>
</dl>
</div>
<div class="panel-footer">
<div class="btn-group btn-group-sm">
<a href="{% url 'nas_app:edit' nas.pk %}" class="btn btn-default">
<span class="glyphicon glyphicon-eye-open"></span>
<span class="hidden-md">{% trans 'View' %}</span>
</a>
</div>
</div>
</div>
</div>
{% empty %}
<div class="col-sm-12">
<h3>{% trans "You don't have gateways" %}</h3>
</div>
{% endfor %}
<div class="col-sm-12">
<div class="btn-group btn-group-sm">
<a href="{% url 'nas_app:add' %}" class="btn btn-default">
<span class="glyphicon glyphicon-plus"></span> {% trans 'Add' %}
</a>
</div>
</div>
</div>
{% endblock %}

42
nas_app/templates/nas_app/nasmodel_update.html

@ -0,0 +1,42 @@
{% extends request.is_ajax|yesno:'bajax.html,base.html' %}
{% load i18n %}
{% load bootstrap3 %}
{% block breadcrumb %}
<ol class="breadcrumb">
<li><span class="glyphicon glyphicon-home"></span></li>
<li><a href="{% url 'nas_app:home' %}">{% trans 'Network access servers' %}</a></li>
<li class="active">{% trans 'Edit gateway' %}</li>
</ol>
{% endblock %}
{% block page-header %}
{% trans 'Change gateways' %}
{% endblock %}
{% block main %}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{% trans 'Change gateway' %}</h3>
</div>
<div class="panel-body">
<form action="{% url 'nas_app:edit' object.pk %}" method="post">{% csrf_token %}
{% bootstrap_form form %}
<div class="btn-group">
<button type="submit" class="btn btn-primary">
<span class="glyphicon glyphicon-save"></span> {% trans 'Save' %}
</button>
{% if perms.nas_app.delete_nasmodel %}
<a href="{% url 'nas_app:del' object.pk %}" class="btn btn-danger btn-modal">
<span class="glyphicon glyphicon-remove"></span> {% trans 'Delete' %}
</a>
{% else %}
<a href="#" class="btn btn-danger" title="{% trans 'Permission denied' %}">
<span class="glyphicon glyphicon-remove"></span> {% trans 'Delete' %}
</a>
{% endif %}
</div>
</form>
</div>
</div>
{% endblock %}

200
nas_app/tests.py

@ -0,0 +1,200 @@
from abc import ABCMeta
from abonapp.models import Abon
from accounts_app.models import UserProfile
from django.conf import settings
from django.utils.translation import gettext_lazy as _, gettext
from django.shortcuts import resolve_url
from django.test import TestCase
from group_app.models import Group
from nas_app.models import NASModel
from nas_app.nas_managers import MikrotikTransmitter
class MyBaseTestCase(metaclass=ABCMeta):
def _client_get_check_login(self, url):
"""
Checks if url is protected from unauthorized access
:param url:
:return: authorized response
"""
r = self.client.get(url)
self.assertRedirects(r, "%s?next=%s" % (getattr(settings, 'LOGIN_URL'), url))
self.client.force_login(self.adminuser)
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
return r
def setUp(self):
grp = Group.objects.create(title='Grp1')
a1 = Abon.objects.create_user(
telephone='+79781234567',
username='abon',
password='passw1'
)
a1.group = grp
a1.save(update_fields=('group',))
my_admin = UserProfile.objects.create_superuser('+79781234567', 'local_superuser', 'ps')
self.adminuser = my_admin
self.abon = a1
self.group = grp
class NASModelTestCase(MyBaseTestCase, TestCase):
def setUp(self):
super(NASModelTestCase, self).setUp()
nas = NASModel.objects.create(
title='Title',
ip_address='192.168.8.12',
ip_port=123,
auth_login='admin',
auth_passw='admin',
default=True,
nas_type='mktk'
)
self.nas = nas
def test_create(self):
url = resolve_url('nas_app:add')
self._client_get_check_login(url)
# test success create nas
r = self.client.post(url, data={
'title': 'Test success nas',
'ip_address': '192.168.8.10',
'ip_port': 1254,
'auth_login': '_',
'auth_passw': '_',
'nas_type': 'mktk'
})
self.assertEqual(r.status_code, 302)
msg = r.cookies.get('messages')
self.assertIn(gettext('New NAS has been created'), msg.output())
NASModel.objects.get(title='Test success nas', ip_address='192.168.8.10', ip_port=1254,
auth_login='_', auth_passw='_')
# test error ip_port big range
r = self.client.post(url, data={
'title': 'New nas',
'ip_address': '192.168.8.13',
'ip_port': 8755877855798,
'auth_login': '_',
'auth_passw': '_'
})
self.assertEqual(r.status_code, 200)
self.assertFormError(response=r, form='form', field='ip_port',
errors=_('Ensure this value is less than or equal to %(limit_value)s.') % {
'limit_value': 65535
})
# test get request
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
# test error duplicates title
r = self.client.post(url, data={
'title': 'Test success nas',
'ip_address': '192.168.8.14',
'ip_port': 2543,
'auth_login': '_w',
'auth_passw': '_v'
})
self.assertEqual(r.status_code, 200)
self.assertFormError(response=r, form='form', field='title',
errors=_('%(model_name)s with this %(field_label)s already exists.') % {
'model_name': NASModel._meta.verbose_name,
'field_label': NASModel._meta.get_field('title').verbose_name
})
# test error duplicates default
r = self.client.post(url, data={
'title': 'New again nas',
'ip_address': '192.168.8.15',
'ip_port': 9873,
'auth_login': '_w',
'auth_passw': '_v',
'default': True
})
self.assertEqual(r.status_code, 200)
self.assertFormError(response=r, form='form', field='default', errors=_('Can be only one default gateway'))
# test error duplicates ip_address
r = self.client.post(url, data={
'title': 'New again nas2',
'ip_address': '192.168.8.10',
'ip_port': 1254,
'auth_login': '_w',
'auth_passw': '_v'
})
self.assertEqual(r.status_code, 200)
self.assertFormError(response=r, form='form', field='ip_address',
errors=_('%(model_name)s with this %(field_label)s already exists.') % {
'model_name': NASModel._meta.verbose_name,
'field_label': NASModel._meta.get_field('ip_address').verbose_name
})
def test_change(self):
url = resolve_url('nas_app:edit', 1)
self._client_get_check_login(url)
# test get request
self.client.get(url)
# test success change
r = self.client.post(url, data={
'title': 'New again nas2 changed',
'ip_address': '192.168.8.12',
'ip_port': 7865,
'auth_login': '_w_c',
'auth_passw': '_v_c',
'nas_type': 'mktk'
})
self.assertRedirects(r, resolve_url('nas_app:edit', 1))
msg = r.cookies.get('messages')
self.assertIn(gettext('Update successfully'), msg.output())
NASModel.objects.get(title='New again nas2 changed', ip_address='192.168.8.12',
ip_port=7865, auth_login='_w_c', auth_passw='_v_c')
def test_delete(self):
url = resolve_url('nas_app:add')
self._client_get_check_login(url)
r = self.client.post(url, data={
'title': 'Test success nas_2',
'ip_address': '192.168.8.11',
'ip_port': 1254,
'auth_login': '_',
'auth_passw': '_',
'nas_type': 'mktk'
})
self.assertEqual(r.status_code, 302)
o = NASModel.objects.get(title='Test success nas_2', ip_address='192.168.8.11', ip_port=1254,
auth_login='_', auth_passw='_')
url = resolve_url('nas_app:del', o.pk)
# test get request
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
# test deleting
r = self.client.post(url)
self.assertRedirects(r, resolve_url('nas_app:home'))
msg = r.cookies.get('messages')
self.assertIn(gettext('Server successfully removed'), msg.output())
try:
NASModel.objects.get(title='Test success nas_2')
raise self.failureException("NAS not removed")
except NASModel.DoesNotExist:
pass
# try to remove default nas
nas_id = self.nas.pk
r = self.client.post(resolve_url('nas_app:del', nas_id))
self.assertRedirects(r, expected_url=resolve_url('nas_app:edit', nas_id))
msg = r.cookies.get('messages')
self.assertIn(gettext('You cannot remove default server'), msg.output())
def test_get_nas_manager(self):
r = self.nas.get_nas_manager_klass()
self.assertIs(r, MikrotikTransmitter)
r = self.nas.get_nas_manager()
self.assertIsInstance(r, MikrotikTransmitter)

13
nas_app/urls.py

@ -0,0 +1,13 @@
from django.conf.urls import url
from nas_app import views
app_name = 'nas_app'
urlpatterns = [
url(r'^$', view=views.NasListView.as_view(), name='home'),
url(r'^add$', view=views.NasCreateView.as_view(), name='add'),
url(r'^(?P<nas_id>\d+)/del$', views.NasDeleteView.as_view(), name='del'),
url(r'^(?P<nas_id>\d+)/edit$', views.NasUpdateView.as_view(), name='edit'),
]

66
nas_app/views.py

@ -0,0 +1,66 @@
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.contrib.messages import MessageFailure
from django.http import HttpResponseRedirect
from django.shortcuts import resolve_url
from django.utils.decorators import method_decorator
from django.utils.translation import gettext_lazy as _
from django.urls import reverse_lazy
from django.views.generic import ListView, CreateView, DeleteView, UpdateView
from guardian.decorators import permission_required_or_403 as permission_required
from guardian.shortcuts import assign_perm
from nas_app.forms import NasForm
from nas_app.models import NASModel
@method_decorator(login_required, name='dispatch')
class NasListView(ListView):
model = NASModel
@method_decorator(login_required, name='dispatch')
@method_decorator(permission_required('nas_app.add_nasmodel'), name='dispatch')
class NasCreateView(CreateView):
model = NASModel
form_class = NasForm
template_name = 'nas_app/nasmodel_add.html'
success_url = reverse_lazy('nas_app:home')
def form_valid(self, form):
r = super(NasCreateView, self).form_valid(form)
assign_perm("nas_app.change_nasmodel", self.request.user, self.object)
assign_perm("nas_app.can_view_nas", self.request.user, self.object)
messages.success(self.request, _('New NAS has been created'))
return r
@method_decorator(login_required, name='dispatch')
@method_decorator(permission_required('nas_app.delete_nasmodel'), name='dispatch')
class NasDeleteView(DeleteView):
model = NASModel
success_url = reverse_lazy('nas_app:home')
pk_url_kwarg = 'nas_id'
def delete(self, request, *args, **kwargs):
try:
r = super(NasDeleteView, self).delete(request, *args, **kwargs)
messages.success(request, _('Server successfully removed'))
return r
except MessageFailure as e:
messages.error(request, e)
failure_url = resolve_url('nas_app:edit', self.object.pk)
return HttpResponseRedirect(failure_url)
@method_decorator(login_required, name='dispatch')
@method_decorator(permission_required('nas_app.change_nasmodel'), name='dispatch')
class NasUpdateView(UpdateView):
model = NASModel
form_class = NasForm
pk_url_kwarg = 'nas_id'
template_name = 'nas_app/nasmodel_update.html'
def form_valid(self, form):
r = super(NasUpdateView, self).form_valid(form)
messages.success(self.request, _('Update successfully'))
return r

2
periodic.py

@ -9,7 +9,7 @@ from django.db import transaction
from django.db.models import signals, Count
from abonapp.models import Abon, AbonTariff, abontariff_pre_delete, PeriodicPayForId, AbonLog
from ip_pool.models import IpLeaseModel
from agent import Transmitter, NasNetworkError, NasFailedResult
from nas_app.nas_managers import NasNetworkError, NasFailedResult
from djing.lib import LogicError

23
statistics/migrations/0003_auto_20180814_1921.py

@ -2,11 +2,11 @@
# Generated by Django 1.11 on 2018-08-14 19:21
from __future__ import unicode_literals
from django.db import migrations
from django.db import migrations, models
from statistics.fields import UnixDateTimeField
class Migration(migrations.Migration):
dependencies = [
('abonapp', '0002_auto_20180808_1448'),
('statistics', '0002_auto_20180808_1236'),
@ -14,7 +14,8 @@ class Migration(migrations.Migration):
operations = [
migrations.RunSQL(
"DROP TABLE `flowcache`; "
(
"DROP TABLE `flowcache`;",
"CREATE TABLE `flowcache` ( "
" `last_time` INT(10) UNSIGNED NOT NULL, "
" `abon_id` INT(11) DEFAULT NULL UNIQUE, "
@ -22,5 +23,21 @@ class Migration(migrations.Migration):
" `packets` INT(10) UNSIGNED NOT NULL, "
" KEY `flowcache_abon_id_91e1085d` (`abon_id`) "
") ENGINE = MEMORY DEFAULT CHARSET = utf8;"
),
state_operations=[
migrations.DeleteModel(name='statcache'),
migrations.CreateModel(
name='statcache',
fields=[
('last_time', UnixDateTimeField()),
('abon', models.OneToOneField('abonapp.Abon', on_delete=models.CASCADE, primary_key=True)),
('octets', models.PositiveIntegerField(default=0)),
('packets', models.PositiveIntegerField(default=0))
],
options={
'db_table': 'flowcache',
},
)
]
)
]

7
templates/base.html

@ -100,6 +100,13 @@
</a>
</li>
{% url 'nas_app:home' as nashome %}
<li{% if nashome in request.path %} class="active"{% endif %}>
<a href="{{ nashome }}">
<span class="glyphicon glyphicon-globe"></span> {% trans 'NAS' %}
</a>
</li>
</ul>
</div>

Loading…
Cancel
Save