From d033dbf8d85945254a93e9f21e83956d38e30f3b Mon Sep 17 00:00:00 2001 From: bashmak Date: Tue, 5 Jun 2018 18:38:47 +0300 Subject: [PATCH] Make registration button --- abonapp/migrations/0001_initial.py | 4 +- abonapp/models.py | 3 +- devapp/base_intr.py | 13 +++-- devapp/dev_types.py | 52 ++++++++++++------- devapp/forms.py | 16 +++--- devapp/locale/ru/LC_MESSAGES/django.po | 11 +++- devapp/migrations/0001_initial.py | 2 +- devapp/migrations/0002_auto_20180409_1318.py | 2 +- devapp/models.py | 19 ++++--- devapp/templates/devapp/dev.html | 3 ++ devapp/urls.py | 3 +- devapp/views.py | 30 ++++++++++- djing/fields.py | 28 +++++++++++ djing/lib/__init__.py | 53 ++++++++++---------- djing/lib/tln/__init__.py | 2 +- djing/lib/tln/tln.py | 3 ++ docs/dev.md | 5 +- statistics/migrations/0001_initial.py | 2 +- statistics/models.py | 2 +- 19 files changed, 175 insertions(+), 78 deletions(-) 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 %} + + + 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, + '' % 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