diff --git a/abonapp/migrations/0001_initial.py b/abonapp/migrations/0001_initial.py
index e929df1..4f516e9 100644
--- a/abonapp/migrations/0001_initial.py
+++ b/abonapp/migrations/0001_initial.py
@@ -7,7 +7,7 @@ from django.conf import settings
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
-from djing import lib
+from djing.fields import MyGenericIPAddressField
import re
@@ -30,7 +30,7 @@ class Migration(migrations.Migration):
models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True,
primary_key=True, serialize=False, to='accounts_app.BaseAccount')),
('ballance', models.FloatField(default=0.0)),
- ('ip_address', lib.MyGenericIPAddressField(blank=True, max_length=8, null=True, protocol='ipv4')),
+ ('ip_address', MyGenericIPAddressField(blank=True, max_length=8, null=True, protocol='ipv4')),
('description', models.TextField(blank=True, null=True, verbose_name='Comment')),
('house', models.CharField(blank=True, max_length=12, null=True, verbose_name='House')),
('is_dynamic_ip', models.BooleanField(default=False)),
diff --git a/abonapp/models.py b/abonapp/models.py
index 650f226..f19fdcb 100644
--- a/abonapp/models.py
+++ b/abonapp/models.py
@@ -14,7 +14,8 @@ 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 group_app.models import Group
-from djing.lib import ip2int, MyGenericIPAddressField, LogicError
+from djing.lib import ip2int, LogicError
+from djing.fields import MyGenericIPAddressField
from djing import IP_ADDR_REGEX
from tariff_app.models import Tariff, PeriodicPay
from bitfield import BitField
diff --git a/devapp/base_intr.py b/devapp/base_intr.py
index 32026a8..e279857 100644
--- a/devapp/base_intr.py
+++ b/devapp/base_intr.py
@@ -45,14 +45,15 @@ class DevBase(object, metaclass=ABCMeta):
def get_template_name(self) -> AnyStr:
"""Return path to html template for device"""
- @abstract_static_method
- def has_attachable_to_subscriber() -> bool:
+ @abstractmethod
+ def has_attachable_to_subscriber(self) -> bool:
"""Can connect device to subscriber"""
@abstract_static_method
def is_use_device_port() -> bool:
"""True if used device port while opt82 authorization"""
+ # fixme: only that is abstract static
@abstract_static_method
def validate_extra_snmp_info(v: str) -> None:
"""
@@ -62,8 +63,12 @@ class DevBase(object, metaclass=ABCMeta):
:param v: String value for validate
"""
- @abstract_static_method
- def monitoring_template(device_instance, *args, **kwargs) -> Optional[str]:
+ @abstractmethod
+ def register_device(self):
+ pass
+
+ @abstractmethod
+ def monitoring_template(self, *args, **kwargs) -> Optional[str]:
"""
Template for monitoring system config
:return: string for config file
diff --git a/devapp/dev_types.py b/devapp/dev_types.py
index b4fe877..47ba213 100644
--- a/devapp/dev_types.py
+++ b/devapp/dev_types.py
@@ -6,7 +6,7 @@ from transliterate import translit
from django.utils.translation import gettext_lazy as _, gettext
from djing.lib import RuTimedelta, safe_int
-from djing.lib.tln.tln import ValidationError as TlnValidationError
+from djing.lib.tln.tln import ValidationError as TlnValidationError, register_onu_ZTE_F660
from .base_intr import DevBase, SNMPBaseWorker, BasePort, DeviceImplementationError, ListOrError
@@ -100,8 +100,7 @@ class DLinkDevice(DevBase, SNMPBaseWorker):
def get_template_name(self):
return 'ports.html'
- @staticmethod
- def has_attachable_to_subscriber():
+ def has_attachable_to_subscriber(self) -> bool:
return True
@staticmethod
@@ -113,8 +112,8 @@ class DLinkDevice(DevBase, SNMPBaseWorker):
# Dlink has no require snmp info
pass
- @staticmethod
- def monitoring_template(device, *args, **kwargs) -> Optional[str]:
+ def monitoring_template(self, *args, **kwargs) -> Optional[str]:
+ device = self.db_instance
return plain_ip_device_mon_template(device, *args, **kwargs)
@@ -182,8 +181,7 @@ class OLTDevice(DevBase, SNMPBaseWorker):
def get_template_name(self):
return 'olt.html'
- @staticmethod
- def has_attachable_to_subscriber():
+ def has_attachable_to_subscriber(self) -> bool:
return False
@staticmethod
@@ -195,8 +193,8 @@ class OLTDevice(DevBase, SNMPBaseWorker):
# Olt has no require snmp info
pass
- @staticmethod
- def monitoring_template(device, *args, **kwargs) -> Optional[str]:
+ def monitoring_template(self, *args, **kwargs) -> Optional[str]:
+ device = self.db_instance
return plain_ip_device_mon_template(device)
@@ -235,8 +233,7 @@ class OnuDevice(DevBase, SNMPBaseWorker):
def get_template_name(self):
return "onu.html"
- @staticmethod
- def has_attachable_to_subscriber():
+ def has_attachable_to_subscriber(self) -> bool:
return True
@staticmethod
@@ -273,8 +270,8 @@ class OnuDevice(DevBase, SNMPBaseWorker):
except ValueError:
raise TlnValidationError(_('Onu snmp field must be en integer'))
- @staticmethod
- def monitoring_template(device, *args, **kwargs) -> Optional[str]:
+ def monitoring_template(self, *args, **kwargs) -> Optional[str]:
+ device = self.db_instance
if not device:
return
host_name = _norm_name("%d%s" % (device.pk, translit(device.comment, language_code='ru', reversed=True)))
@@ -344,16 +341,15 @@ class EltexSwitch(DLinkDevice):
tm = RuTimedelta(timedelta(seconds=uptimestamp / 100)) or RuTimedelta(timedelta())
return tm
- @staticmethod
- def has_attachable_to_subscriber():
+ def has_attachable_to_subscriber(self) -> bool:
return True
@staticmethod
def is_use_device_port():
return False
- @staticmethod
- def monitoring_template(device, *args, **kwargs) -> Optional[str]:
+ def monitoring_template(self, *args, **kwargs) -> Optional[str]:
+ device = self.db_instance
return plain_ip_device_mon_template(device)
@@ -432,6 +428,9 @@ class Olt_ZTE_C320(OLTDevice):
def get_template_name(self):
return 'olt_ztec320.html'
+ def register_device(self):
+ pass
+
class ZteOnuDevice(OnuDevice):
@staticmethod
@@ -472,8 +471,8 @@ class ZteOnuDevice(OnuDevice):
except ValueError:
raise TlnValidationError(_('Zte onu snmp field must be two dot separated integers'))
- @staticmethod
- def monitoring_template(device, *args, **kwargs) -> Optional[str]:
+ def monitoring_template(self, *args, **kwargs) -> Optional[str]:
+ device = self.db_instance
if not device:
return
host_name = _norm_name("%d%s" % (device.pk, translit(device.comment, language_code='ru', reversed=True)))
@@ -495,3 +494,18 @@ class ZteOnuDevice(OnuDevice):
"}\n"
)
return '\n'.join(i for i in r if i)
+
+ def register_device(self):
+ device = self.db_instance
+ ip = None
+ if device.ip_address:
+ ip = device.ip_address
+ elif device.parent_dev:
+ ip = device.parent_dev.ip_address
+ if ip:
+ mac = str(device.mac_addr).encode()
+ sn = b"ZTEG%s" % b''.join(mac.split(b':')[-4:]).upper()
+ register_onu_ZTE_F660(
+ olt_ip=ip, onu_sn=sn, login_passwd=(b'admin', b'2ekc3'),
+ onu_mac=mac
+ )
diff --git a/devapp/forms.py b/devapp/forms.py
index 6511a58..6502668 100644
--- a/devapp/forms.py
+++ b/devapp/forms.py
@@ -22,13 +22,15 @@ class DeviceForm(forms.ModelForm):
if snmp_extra is None:
return
device = self.instance
- manager_class = device.get_manager_klass()
- try:
- manager_class.validate_extra_snmp_info(snmp_extra)
- except TlnValidationError as e:
- raise ValidationError(
- e, code='invalid'
- )
+ # fixme: if creating device than check disabled
+ if device.pk is not None:
+ manager_class = device.get_manager_klass()
+ try:
+ manager_class.validate_extra_snmp_info(snmp_extra)
+ except TlnValidationError as e:
+ raise ValidationError(
+ e, code='invalid'
+ )
return snmp_extra
class Meta:
diff --git a/devapp/locale/ru/LC_MESSAGES/django.po b/devapp/locale/ru/LC_MESSAGES/django.po
index c21ce0d..c9b8f76 100644
--- a/devapp/locale/ru/LC_MESSAGES/django.po
+++ b/devapp/locale/ru/LC_MESSAGES/django.po
@@ -234,7 +234,7 @@ msgstr "Сохранить"
#: templates/devapp/add_dev.html:75 templates/devapp/dev.html:73
#: templates/devapp/fix_dev_group.html:67
msgid "Reset"
-msgstr "Сбросить"
+msgstr "Сбросить форму"
#: templates/devapp/custom_dev_page/olt.html:10
#: templates/devapp/custom_dev_page/olt_ztec320.html:11
@@ -609,3 +609,12 @@ msgstr "Посмотреть"
msgid "Unregistered units"
msgstr "Незарегистрированные юниты"
+
+msgid "Register device"
+msgstr "Зарегистрировать устройство"
+
+msgid "Unregistered onu not found"
+msgstr "Незарегистрированные ONU не найдены"
+
+msgid "Process locked by another process"
+msgstr "Процесс занят другой задачей, подождите чуть и попробуйте ещё"
diff --git a/devapp/migrations/0001_initial.py b/devapp/migrations/0001_initial.py
index 9103f5f..9ac128b 100644
--- a/devapp/migrations/0001_initial.py
+++ b/devapp/migrations/0001_initial.py
@@ -5,7 +5,7 @@ from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
import djing.fields
-from djing.lib import MyGenericIPAddressField
+from djing.fields import MyGenericIPAddressField
class Migration(migrations.Migration):
diff --git a/devapp/migrations/0002_auto_20180409_1318.py b/devapp/migrations/0002_auto_20180409_1318.py
index 891cd0c..7714d3d 100644
--- a/devapp/migrations/0002_auto_20180409_1318.py
+++ b/devapp/migrations/0002_auto_20180409_1318.py
@@ -3,7 +3,7 @@
from __future__ import unicode_literals
from django.db import migrations
-from djing.lib import MyGenericIPAddressField
+from djing.fields import MyGenericIPAddressField
class Migration(migrations.Migration):
diff --git a/devapp/models.py b/devapp/models.py
index e3c0eaf..29dff3b 100644
--- a/devapp/models.py
+++ b/devapp/models.py
@@ -5,8 +5,8 @@ from django.db import models
from django.conf import settings
from django.utils.translation import gettext_lazy as _
-from djing.fields import MACAddressField
-from djing.lib import MyGenericIPAddressField, MyChoicesAdapter
+from djing.fields import MACAddressField, MyGenericIPAddressField
+from djing.lib import MyChoicesAdapter
from group_app.models import Group
from . import dev_types
from .base_intr import DevBase
@@ -84,9 +84,9 @@ class Device(models.Model):
return self._cached_manager
# Can attach device to subscriber in subscriber page
- def has_attachable_to_subscriber(self):
- mngr_class = self.get_manager_klass()
- return mngr_class.has_attachable_to_subscriber()
+ def has_attachable_to_subscriber(self) -> bool:
+ mngr = self.get_manager_object()
+ return mngr.has_attachable_to_subscriber()
def __str__(self):
return "%s: (%s) %s %s" % (self.comment, self.get_devtype_display(), self.ip_address or '', self.mac_addr or '')
@@ -106,8 +106,13 @@ class Device(models.Model):
run((filepath, param, newmac, code))
def generate_config_template(self) -> Optional[AnyStr]:
- mng_class = self.get_manager_klass()
- return mng_class.monitoring_template(self)
+ mng = self.get_manager_object()
+ return mng.monitoring_template()
+
+ def register_device(self):
+ mng = self.get_manager_object()
+ # if hasattr(mng, 'register_device') and callable(mng.register_device):
+ mng.register_device()
class Port(models.Model):
diff --git a/devapp/templates/devapp/dev.html b/devapp/templates/devapp/dev.html
index 0b4e77a..1004238 100644
--- a/devapp/templates/devapp/dev.html
+++ b/devapp/templates/devapp/dev.html
@@ -69,6 +69,9 @@
{% trans 'Delete' %}
{% endif %}
+
+ {% trans 'Register device' %}
+
diff --git a/devapp/urls.py b/devapp/urls.py
index 62353a8..50db937 100644
--- a/devapp/urls.py
+++ b/devapp/urls.py
@@ -18,7 +18,8 @@ urlpatterns = [
name='fix_port_conflict'),
url(r'^(?P\d+)/(?P\d+)/ports/(?P\d+)/show_subscriber_on_port$',
views.ShowSubscriberOnPort.as_view(), name='show_subscriber_on_port'),
- url(r'^(\d+)/(?P\d+)/ports_add', views.add_ports, name='add_ports'),
+ url(r'^(\d+)/(?P\d+)/ports_add/$', views.add_ports, name='add_ports'),
+ url(r'^(\d+)/(?P\d+)/regster_device/$', views.regster_device, name='dev_register'),
url(r'^(\d+)/(?P\d+)/(?P\d+)_(?P[0-1]{1})$', views.toggle_port, name='port_toggle'),
url(r'^(?P\d+)/(?P\d+)/(?P\d+)/del$', views.delete_single_port, name='del_port'),
url(r'^(?P\d+)/(?P\d+)/(?P\d+)/edit$', views.edit_single_port, name='edit_port'),
diff --git a/devapp/views.py b/devapp/views.py
index ba26380..ab52628 100644
--- a/devapp/views.py
+++ b/devapp/views.py
@@ -14,8 +14,9 @@ from django.views.generic import DetailView, DeleteView, UpdateView, CreateView
from devapp.base_intr import DeviceImplementationError
from djing.lib.decorators import only_admins, hash_auth_view
-from djing.lib import safe_int
+from djing.lib import safe_int, ProcessLocked
from abonapp.models import Abon
+from djing.lib.tln import ZteOltConsoleError, OnuZteRegisterError
from group_app.models import Group
from accounts_app.models import UserProfile
from django.conf import settings
@@ -693,3 +694,30 @@ class DevicesGetListView(global_base_views.SecureApiView):
if isinstance(r['mac_addr'], EUI):
r['mac_addr'] = int(r['mac_addr'])
return tuple(res)
+
+
+@login_required
+@json_view
+def regster_device(request, device_id: str):
+ def format_msg(msg: str, icon: str):
+ return ' '.join((
+ '' % icon,
+ '%s' % msg
+ ))
+ device = get_object_or_404(Device, pk=device_id)
+ status = 1
+ try:
+ device.register_device()
+ status = 0
+ except OnuZteRegisterError:
+ text = format_msg(gettext('Unregistered onu not found'), 'eye-close')
+ except (ConnectionRefusedError, ZteOltConsoleError) as e:
+ text = format_msg(e, 'exclamation-sign')
+ except ProcessLocked:
+ text = format_msg(gettext('Process locked by another process'), 'time')
+ else:
+ text = format_msg(msg='ok', icon='ok')
+ return {
+ 'status': status,
+ 'dat': text
+ }
diff --git a/djing/fields.py b/djing/fields.py
index b25fde9..68328e5 100644
--- a/djing/fields.py
+++ b/djing/fields.py
@@ -4,6 +4,8 @@
from django.core.exceptions import ValidationError
from django.db import models
from netaddr import EUI, AddrFormatError
+
+from djing.lib import ip2int, int2ip
from .formfields import MACAddressField as MACAddressFormField
from . import default_dialect
import warnings
@@ -107,3 +109,29 @@ try:
add_introspection_rules([], ["^macaddress\.fields\.MACAddressField"])
except ImportError:
pass
+
+
+class MyGenericIPAddressField(models.GenericIPAddressField):
+ description = "Int32 notation ip address"
+
+ def __init__(self, protocol='ipv4', *args, **kwargs):
+ super(MyGenericIPAddressField, self).__init__(protocol=protocol, *args, **kwargs)
+ self.max_length = 8
+
+ def get_prep_value(self, value):
+ # strIp to Int
+ value = super(MyGenericIPAddressField, self).get_prep_value(value)
+ return ip2int(value)
+
+ def to_python(self, value):
+ return value
+
+ def get_internal_type(self):
+ return 'PositiveIntegerField'
+
+ @staticmethod
+ def from_db_value(value, expression, connection, context):
+ return int2ip(value) if value != 0 else None
+
+ def int_ip(self):
+ return ip2int(self)
diff --git a/djing/lib/__init__.py b/djing/lib/__init__.py
index 9373b6a..0442c0a 100644
--- a/djing/lib/__init__.py
+++ b/djing/lib/__init__.py
@@ -1,9 +1,9 @@
import socket
import struct
+from functools import wraps
from hashlib import sha256
from datetime import timedelta
from collections import Iterator
-from django.db import models
def ip2int(addr):
@@ -34,32 +34,6 @@ def safe_int(i):
return 0
-class MyGenericIPAddressField(models.GenericIPAddressField):
- description = "Int32 notation ip address"
-
- def __init__(self, protocol='ipv4', *args, **kwargs):
- super(MyGenericIPAddressField, self).__init__(protocol=protocol, *args, **kwargs)
- self.max_length = 8
-
- def get_prep_value(self, value):
- # strIp to Int
- value = super(MyGenericIPAddressField, self).get_prep_value(value)
- return ip2int(value)
-
- def to_python(self, value):
- return value
-
- def get_internal_type(self):
- return 'PositiveIntegerField'
-
- @staticmethod
- def from_db_value(value, expression, connection, context):
- return int2ip(value) if value != 0 else None
-
- def int_ip(self):
- return ip2int(self)
-
-
# Предназначен для Django CHOICES чтоб можно было передавать классы вместо просто описания поля,
# классы передавать для того чтоб по значению кода из базы понять какой класс нужно взять для нужной функциональности.
# Например по коду в базе вам нужно определять как считать тариф абонента, что реализовано в возвращаемом классе.
@@ -151,3 +125,28 @@ def check_sign(get_list, sign):
hashed = '_'.join(get_list)
my_sign = calc_hash(hashed)
return sign == my_sign
+
+#
+# only one process for function
+#
+
+
+class ProcessLocked(OSError):
+ pass
+
+
+def process_lock(fn):
+ @wraps(fn)
+ def wrapped(*args, **kwargs):
+ s = None
+ try:
+ s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ # Create an abstract socket, by prefixing it with null.
+ s.bind('\0postconnect_djing_lock_func_%s' % fn.__name__)
+ return fn(*args, **kwargs)
+ except socket.error:
+ raise ProcessLocked
+ finally:
+ if s is not None:
+ s.close()
+ return wrapped
diff --git a/djing/lib/tln/__init__.py b/djing/lib/tln/__init__.py
index 5f19e0c..b7cc8b5 100644
--- a/djing/lib/tln/__init__.py
+++ b/djing/lib/tln/__init__.py
@@ -1,4 +1,4 @@
from .tln import *
-__all__ = ('TelnetApi', 'ValidationError', 'ZTEFiberNumberNotFound', 'ZTEFiberIsFull',
+__all__ = ('TelnetApi', 'ValidationError', 'ZTEFiberIsFull',
'OnuZteRegisterError', 'ZteOltConsoleError', 'register_onu_ZTE_F660')
diff --git a/djing/lib/tln/tln.py b/djing/lib/tln/tln.py
index 48ec8b7..e39e855 100755
--- a/djing/lib/tln/tln.py
+++ b/djing/lib/tln/tln.py
@@ -5,6 +5,8 @@ from telnetlib import Telnet
from time import sleep
from typing import Generator, Dict, Optional, Tuple
+from djing.lib import process_lock
+
class ZteOltConsoleError(Exception):
pass
@@ -192,6 +194,7 @@ class OltZTERegister(TelnetApi):
))) + r
+@process_lock
def register_onu_ZTE_F660(olt_ip: str, onu_sn: bytes, login_passwd: Tuple[bytes, bytes], onu_mac: bytes):
onu_type = b'ZTE-F660'
line_profile = b'ZTE-F660-LINE'
diff --git a/docs/dev.md b/docs/dev.md
index ce2fff9..b9f728e 100644
--- a/docs/dev.md
+++ b/docs/dev.md
@@ -77,8 +77,7 @@ class EltexSwitch(DLinkDevice):
tm = RuTimedelta(timedelta(seconds=uptimestamp/100)) or RuTimedelta(timedelta())
return tm
- @staticmethod
- def has_attachable_to_subscriber():
+ def has_attachable_to_subscriber(self) -> bool:
return False
@staticmethod
@@ -99,7 +98,7 @@ class EltexSwitch(DLinkDevice):
Метод **@uptime**, понятно что возвращает, укажите нужный OID. Вернётся тип *RuTimedelta*, это переопределённый тип **timedelta**, я его реализовал
для локализации временного промежутка на русский.
-Статический метод **@has_attachable_to_subscriber** возвращает правду если это устройство можно привязать к абоненту.
+Метод **@has_attachable_to_subscriber** возвращает правду если это устройство можно привязать к абоненту.
Например у Dlink стоит True потому что Dlink стоит во многих местах на доступе, и его порты принадлежат
абонентам при авторизации.
diff --git a/statistics/migrations/0001_initial.py b/statistics/migrations/0001_initial.py
index 4148c97..7131ef2 100644
--- a/statistics/migrations/0001_initial.py
+++ b/statistics/migrations/0001_initial.py
@@ -3,7 +3,7 @@
from __future__ import unicode_literals
from django.db import migrations, models
-from djing.lib import MyGenericIPAddressField
+from djing.fields import MyGenericIPAddressField
import statistics.fields
diff --git a/statistics/models.py b/statistics/models.py
index 13ff2b4..ceb0736 100644
--- a/statistics/models.py
+++ b/statistics/models.py
@@ -3,7 +3,7 @@ from datetime import datetime, timedelta, date, time
from django.db import models, connection, ProgrammingError
from django.utils.timezone import now
-from djing.lib import MyGenericIPAddressField
+from djing.fields import MyGenericIPAddressField
from .fields import UnixDateTimeField