From c8e9d0906ad5907f1777033c09338b7204f1e691 Mon Sep 17 00:00:00 2001 From: bashmak Date: Tue, 29 May 2018 14:47:14 +0300 Subject: [PATCH 1/5] Fix initial date --- abonapp/forms.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/abonapp/forms.py b/abonapp/forms.py index 973b587..8b59cc3 100644 --- a/abonapp/forms.py +++ b/abonapp/forms.py @@ -98,12 +98,6 @@ class AbonForm(forms.ModelForm): class PassportForm(forms.ModelForm): - def __init__(self, *args, **kwargs): - kwargs.update({'initial': { - 'date_of_acceptance': datetime(year=2014, month=6, day=1).strftime("%Y-%m-%d") - }}) - super(PassportForm, self).__init__(*args, **kwargs) - class Meta: model = models.PassportInfo exclude = ('abon',) @@ -111,7 +105,7 @@ class PassportForm(forms.ModelForm): 'series': forms.TextInput(attrs={'required': '', 'pattern': '^\d{4}$'}), 'number': forms.TextInput(attrs={'required': '', 'pattern': '^\d{6}$'}), 'distributor': forms.TextInput(attrs={'required': ''}), - 'date_of_acceptance': forms.DateInput(attrs={'class': 'form-control', 'required': ''}) + 'date_of_acceptance': forms.DateInput(attrs={'class': 'form-control', 'required': ''}, format='%Y-%m-%d') } From 00cbf9287dd4d0b9975ad8f9e868e64786a4ee32 Mon Sep 17 00:00:00 2001 From: bashmak Date: Thu, 31 May 2018 13:17:46 +0300 Subject: [PATCH 2/5] refactoring: mydefs moved to djing.lib --- abonapp/migrations/0001_initial.py | 4 +- abonapp/models.py | 2 +- abonapp/pay_systems.py | 2 +- abonapp/views.py | 106 ++++++----- accounts_app/views.py | 17 +- agent/mod_mikrotik.py | 2 +- agent/structs.py | 34 ++-- clientsideapp/views.py | 2 +- devapp/migrations/0001_initial.py | 4 +- devapp/migrations/0002_auto_20180409_1318.py | 4 +- dialing_app/views.py | 2 +- djing/lib/__init__.py | 186 +++++++++++++++++++ djing/settings.py | 2 +- docs/dev.md | 2 +- mapapp/views.py | 2 +- periodic.py | 2 +- statistics/migrations/0001_initial.py | 5 +- statistics/models.py | 2 +- tariff_app/base_intr.py | 4 +- tariff_app/models.py | 2 +- tariff_app/views.py | 8 +- taskapp/handle.py | 2 +- taskapp/migrations/0001_initial.py | 2 +- taskapp/views.py | 7 +- 24 files changed, 294 insertions(+), 111 deletions(-) create mode 100644 djing/lib/__init__.py diff --git a/abonapp/migrations/0001_initial.py b/abonapp/migrations/0001_initial.py index 2877a67..e929df1 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 -import mydefs +from djing import lib 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', mydefs.MyGenericIPAddressField(blank=True, max_length=8, null=True, protocol='ipv4')), + ('ip_address', lib.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 6cf8e74..650f226 100644 --- a/abonapp/models.py +++ b/abonapp/models.py @@ -14,7 +14,7 @@ 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 mydefs import MyGenericIPAddressField, ip2int, LogicError +from djing.lib import ip2int, MyGenericIPAddressField, LogicError from djing import IP_ADDR_REGEX from tariff_app.models import Tariff, PeriodicPay from bitfield import BitField diff --git a/abonapp/pay_systems.py b/abonapp/pay_systems.py index 90b025d..6fe737e 100644 --- a/abonapp/pay_systems.py +++ b/abonapp/pay_systems.py @@ -1,6 +1,6 @@ from hashlib import md5 from django.utils import timezone -from mydefs import safe_int, safe_float +from djing.lib import safe_int, safe_float from .models import Abon, AllTimePayLog from django.db import DatabaseError from django.conf import settings diff --git a/abonapp/views.py b/abonapp/views.py index 5139d2a..5a36b85 100644 --- a/abonapp/views.py +++ b/abonapp/views.py @@ -20,7 +20,6 @@ from tariff_app.models import Tariff from agent import NasFailedResult, Transmitter, NasNetworkError from . import forms from . import models -import mydefs from devapp.models import Device, Port as DevPort from datetime import datetime, date, timedelta from taskapp.models import Task @@ -30,6 +29,7 @@ from group_app.models import Group from guardian.shortcuts import get_objects_for_user, assign_perm from guardian.decorators import permission_required_or_403 as permission_required from djing import ping +from djing import lib from djing.global_base_views import OrderingMixin, BaseListWithFiltering, SecureApiView PAGINATION_ITEMS_PER_PAGE = getattr(settings, 'PAGINATION_ITEMS_PER_PAGE', 10) @@ -40,14 +40,14 @@ class BaseAbonListView(OrderingMixin, BaseListWithFiltering): http_method_names = ('get',) -@method_decorator((login_required, mydefs.only_admins), name='dispatch') +@method_decorator((login_required, lib.only_admins), name='dispatch') class PeoplesListView(BaseAbonListView): context_object_name = 'peoples' template_name = 'abonapp/peoples.html' def get_queryset(self): - street_id = mydefs.safe_int(self.request.GET.get('street')) - gid = mydefs.safe_int(self.kwargs.get('gid')) + street_id = lib.safe_int(self.request.GET.get('street')) + gid = lib.safe_int(self.kwargs.get('gid')) peoples_list = models.Abon.objects.all().select_related('group', 'street', 'current_tariff') if street_id > 0: peoples_list = peoples_list.filter(group__pk=gid, street=street_id) @@ -61,7 +61,7 @@ class PeoplesListView(BaseAbonListView): abon.stat_cache = StatCache.objects.get(ip=abon.ip_address) except StatCache.DoesNotExist: pass - except mydefs.LogicError as e: + except lib.LogicError as e: messages.warning(self.request, e) ordering = self.get_ordering() if ordering and isinstance(ordering, str): @@ -70,7 +70,7 @@ class PeoplesListView(BaseAbonListView): return peoples_list def get_context_data(self, **kwargs): - gid = mydefs.safe_int(self.kwargs.get('gid')) + gid = lib.safe_int(self.kwargs.get('gid')) if gid < 1: return HttpResponseBadRequest('group id is broken') group = get_object_or_404(Group, pk=gid) @@ -80,12 +80,12 @@ class PeoplesListView(BaseAbonListView): context = super(PeoplesListView, self).get_context_data(**kwargs) context['streets'] = models.AbonStreet.objects.filter(group=gid) - context['street_id'] = mydefs.safe_int(self.request.GET.get('street')) + context['street_id'] = lib.safe_int(self.request.GET.get('street')) context['group'] = group return context -@method_decorator((login_required, mydefs.only_admins), name='dispatch') +@method_decorator((login_required, lib.only_admins), name='dispatch') class GroupListView(BaseAbonListView): context_object_name = 'groups' template_name = 'abonapp/group_list.html' @@ -98,7 +98,7 @@ class GroupListView(BaseAbonListView): return queryset -@method_decorator((login_required, mydefs.only_admins), name='dispatch') +@method_decorator((login_required, lib.only_admins), name='dispatch') @method_decorator(permission_required('abonapp.add_abon'), name='dispatch') class AbonCreateView(CreateView): group = None @@ -142,9 +142,9 @@ class AbonCreateView(CreateView): messages.success(self.request, _('create abon success msg')) self.abon = abon return super(AbonCreateView, self).form_valid(form) - except (IntegrityError, NasFailedResult, NasNetworkError, mydefs.LogicError) as e: + except (IntegrityError, NasFailedResult, NasNetworkError, lib.LogicError) as e: messages.error(self.request, e) - except mydefs.MultipleException as errs: + except lib.MultipleException as errs: for err in errs.err_list: messages.error(self.request, err) return self.render_to_response(self.get_context_data(form=form)) @@ -154,7 +154,7 @@ class AbonCreateView(CreateView): return super(AbonCreateView, self).form_invalid(form) -@method_decorator((login_required, mydefs.only_admins), name='dispatch') +@method_decorator((login_required, lib.only_admins), name='dispatch') @method_decorator(permission_required('abonapp.delete_abon'), name='dispatch') class DelAbonDeleteView(DeleteView): model = models.Abon @@ -181,7 +181,7 @@ class DelAbonDeleteView(DeleteView): messages.error(self.request, e) except NasFailedResult as e: messages.error(self.request, _("NAS says: '%s'") % e) - except mydefs.MultipleException as errs: + except lib.MultipleException as errs: for err in errs.err_list: messages.error(self.request, err) return HttpResponseRedirect(self.success_url) @@ -196,7 +196,7 @@ def abonamount(request, gid, uname): if request.method == 'POST': abonuname = request.POST.get('abonuname') if abonuname == uname: - amnt = mydefs.safe_float(request.POST.get('amount')) + amnt = lib.safe_float(request.POST.get('amount')) abon.add_ballance(request.user, amnt, comment=_('fill account through admin side')) abon.save(update_fields=('ballance',)) messages.success(request, _('Account filled successfully on %.2f') % amnt) @@ -205,7 +205,7 @@ def abonamount(request, gid, uname): messages.error(request, _('I not know the account id')) except (NasNetworkError, NasFailedResult) as e: messages.error(request, e) - except mydefs.MultipleException as errs: + except lib.MultipleException as errs: for err in errs.err_list: messages.error(request, err) return render_to_text('abonapp/modal_abonamount.html', { @@ -214,7 +214,7 @@ def abonamount(request, gid, uname): }, request=request) -@method_decorator((login_required, mydefs.only_admins), name='dispatch') +@method_decorator((login_required, lib.only_admins), name='dispatch') @method_decorator(permission_required('group_app.can_view_group', (Group, 'pk', 'gid')), name='dispatch') class DebtsListView(BaseAbonListView): context_object_name = 'invoices' @@ -232,7 +232,7 @@ class DebtsListView(BaseAbonListView): return context -@method_decorator((login_required, mydefs.only_admins), name='dispatch') +@method_decorator((login_required, lib.only_admins), name='dispatch') @method_decorator(permission_required('group_app.can_view_group', (Group, 'pk', 'gid')), name='dispatch') class PayHistoryListView(BaseAbonListView): context_object_name = 'pay_history' @@ -252,7 +252,7 @@ class PayHistoryListView(BaseAbonListView): @login_required -@mydefs.only_admins +@lib.only_admins def abon_services(request, gid, uname): grp = get_object_or_404(Group, pk=gid) if not request.user.has_perm('group_app.can_view_group', grp): @@ -277,7 +277,7 @@ def abon_services(request, gid, uname): }) -@method_decorator((login_required, mydefs.only_admins), name='dispatch') +@method_decorator((login_required, lib.only_admins), name='dispatch') @method_decorator(permission_required('abonapp.change_abon'), name='post') class AbonHomeUpdateView(UpdateView): model = models.Abon @@ -291,11 +291,11 @@ class AbonHomeUpdateView(UpdateView): def dispatch(self, request, *args, **kwargs): try: return super(AbonHomeUpdateView, self).dispatch(request, *args, **kwargs) - except mydefs.LogicError as e: + except lib.LogicError as e: messages.error(request, e) except (NasFailedResult, NasNetworkError) as e: messages.error(request, e) - except mydefs.MultipleException as errs: + except lib.MultipleException as errs: for err in errs.err_list: messages.error(request, err) return self.render_to_response(self.get_context_data()) @@ -342,11 +342,11 @@ class AbonHomeUpdateView(UpdateView): def get_context_data(self, **kwargs): abon = self.object - dev = getattr(abon, 'device') + device = getattr(abon, 'device') context = { 'group': self.group, - 'device': dev, - 'dev_ports': DevPort.objects.filter(device=dev) if dev else None + 'device': device, + 'dev_ports': DevPort.objects.filter(device=device) if device else None } context.update(kwargs) return super(AbonHomeUpdateView, self).get_context_data(**context) @@ -354,9 +354,9 @@ class AbonHomeUpdateView(UpdateView): def get_success_url(self): abon = self.object return resolve_url('abonapp:abon_home', - gid=getattr(abon.group, 'pk', 0), - uname=abon.username - ) + gid=getattr(abon.group, 'pk', 0), + uname=abon.username + ) @transaction.atomic @@ -377,7 +377,7 @@ def add_invoice(request, gid, uname): try: if request.method == 'POST': - curr_amount = mydefs.safe_int(request.POST.get('curr_amount')) + curr_amount = lib.safe_int(request.POST.get('curr_amount')) comment = request.POST.get('comment') newinv = models.InvoiceForPayment() @@ -395,7 +395,7 @@ def add_invoice(request, gid, uname): except (NasNetworkError, NasFailedResult) as e: messages.error(request, e) - except mydefs.MultipleException as errs: + except lib.MultipleException as errs: for err in errs.err_list: messages.error(request, err) return render(request, 'abonapp/addInvoice.html', { @@ -406,7 +406,7 @@ def add_invoice(request, gid, uname): @login_required -@mydefs.only_admins +@lib.only_admins @permission_required('abonapp.can_buy_tariff') @transaction.atomic def pick_tariff(request, gid, uname): @@ -429,14 +429,14 @@ def pick_tariff(request, gid, uname): abon.sync_with_nas(created=False) messages.success(request, _('Tariff has been picked')) return redirect('abonapp:abon_services', gid=gid, uname=abon.username) - except (mydefs.LogicError, NasFailedResult) as e: + except (lib.LogicError, NasFailedResult) as e: messages.error(request, e) except NasNetworkError as e: messages.error(request, e) return redirect('abonapp:abon_services', gid=gid, uname=abon.username) except Tariff.DoesNotExist: messages.error(request, _('Tariff your picked does not exist')) - except mydefs.MultipleException as errs: + except lib.MultipleException as errs: for err in errs.err_list: messages.error(request, err) except ValueError as e: @@ -446,7 +446,7 @@ def pick_tariff(request, gid, uname): 'tariffs': tariffs, 'abon': abon, 'group': grp, - 'selected_tariff': mydefs.safe_int(request.GET.get('selected_tariff')) + 'selected_tariff': lib.safe_int(request.GET.get('selected_tariff')) }) @@ -463,7 +463,7 @@ def unsubscribe_service(request, gid, uname, abon_tariff_id): messages.error(request, e) except NasNetworkError as e: messages.warning(request, e) - except mydefs.MultipleException as errs: + except lib.MultipleException as errs: for err in errs.err_list: messages.error(request, err) return redirect('abonapp:abon_services', gid=gid, uname=uname) @@ -550,7 +550,7 @@ class PassportUpdateView(UpdateView): @login_required -@mydefs.only_admins +@lib.only_admins def chgroup_tariff(request, gid): grp = get_object_or_404(Group, pk=gid) if not request.user.has_perm('group_app.change_group', grp): @@ -578,8 +578,7 @@ def dev(request, gid, uname): try: abon = models.Abon.objects.get(username=uname) if request.method == 'POST': - dev = Device.objects.get(pk=request.POST.get('dev')) - abon.device = dev + abon.device = Device.objects.get(pk=request.POST.get('dev')) abon.save(update_fields=('device',)) messages.success(request, _('Device has successfully attached')) return redirect('abonapp:abon_home', gid=gid, uname=uname) @@ -683,7 +682,7 @@ def make_extra_field(request, gid, uname): except (NasNetworkError, NasFailedResult) as e: messages.error(request, e) frm = forms.ExtraFieldForm() - except mydefs.MultipleException as errs: + except lib.MultipleException as errs: for err in errs.err_list: messages.error(request, err) frm = forms.ExtraFieldForm() @@ -731,7 +730,7 @@ def abon_ping(request): text = ' %s' % _('no ping') try: if ip is None: - raise mydefs.LogicError(_('Ip not passed')) + raise lib.LogicError(_('Ip not passed')) tm = Transmitter() ping_result = tm.ping(ip) if ping_result is None: @@ -756,7 +755,7 @@ def abon_ping(request): text = ' %s' % _('ping ok') + ' ' + str(ping_result) status = True - except (NasFailedResult, mydefs.LogicError) as e: + except (NasFailedResult, lib.LogicError) as e: messages.error(request, e) except NasNetworkError as e: messages.warning(request, e) @@ -767,7 +766,7 @@ def abon_ping(request): } -@method_decorator((login_required, mydefs.only_admins,), name='dispatch') +@method_decorator((login_required, lib.only_admins,), name='dispatch') class DialsListView(BaseAbonListView): context_object_name = 'logs' template_name = 'abonapp/dial_log.html' @@ -813,7 +812,7 @@ def save_user_dev_port(request, gid, uname): if request.method != 'POST': messages.error(request, _('Method is not POST')) return redirect('abonapp:abon_home', gid, uname) - user_port = mydefs.safe_int(request.POST.get('user_port')) + user_port = lib.safe_int(request.POST.get('user_port')) is_dynamic_ip = request.POST.get('is_dynamic_ip') is_dynamic_ip = True if is_dynamic_ip == 'on' else False try: @@ -948,7 +947,7 @@ def tel_add(request, gid, uname): @permission_required('abnapp.delete_additionaltelephone') def tel_del(request, gid, uname): try: - tid = mydefs.safe_int(request.GET.get('tid')) + tid = lib.safe_int(request.GET.get('tid')) tel = models.AdditionalTelephone.objects.get(pk=tid) tel.delete() messages.success(request, _('Additional telephone successfully deleted')) @@ -964,17 +963,17 @@ def phonebook(request, gid): t1 = models.Abon.objects.filter(group__id=int(gid)).only('telephone', 'fio').values_list('telephone', 'fio') t2 = models.AdditionalTelephone.objects.filter(abon__group__id=gid).only('telephone', 'owner_name').values_list( 'telephone', 'owner_name') - tels = list(t1) + list(t2) + telephones = tuple(t1) + tuple(t2) if res_format == 'csv': import csv response = HttpResponse(content_type='text/csv') response['Content-Disposition'] = 'attachment; filename="phones.csv"' writer = csv.writer(response, quoting=csv.QUOTE_NONNUMERIC) - for row in tels: + for row in telephones: writer.writerow(row) return response return render_to_text('abonapp/modal_phonebook.html', { - 'tels': tels, + 'tels': telephones, 'gid': gid }, request=request) @@ -1029,7 +1028,7 @@ def reset_ip(request, gid, uname): @login_required -@mydefs.only_admins +@lib.only_admins def fin_report(request): q = models.AllTimePayLog.objects.by_days() res_format = request.GET.get('f') @@ -1089,7 +1088,7 @@ def del_periodic_pay(request, gid, uname, periodic_pay_id): return redirect('abonapp:abon_services', gid, uname) -@method_decorator((login_required, mydefs.only_admins,), name='dispatch') +@method_decorator((login_required, lib.only_admins,), name='dispatch') class EditSibscriberMarkers(UpdateView): http_method_names = ('get', 'post') template_name = 'abonapp/modal_user_markers.html' @@ -1120,7 +1119,7 @@ class EditSibscriberMarkers(UpdateView): # API's @login_required -@mydefs.only_admins +@lib.only_admins @json_view def abons(request): ablist = ({ @@ -1145,7 +1144,7 @@ def abons(request): @login_required -@mydefs.only_admins +@lib.only_admins @json_view def search_abon(request): word = request.GET.get('s') @@ -1172,15 +1171,14 @@ class DhcpLever(SecureApiView): @staticmethod def on_dhcp_event(data: Dict) -> Optional[str]: - ''' + """ data = { 'client_ip': ip2int('127.0.0.1'), 'client_mac': 'aa:bb:cc:dd:ee:ff', 'switch_mac': 'aa:bb:cc:dd:ee:ff', 'switch_port': 3, 'cmd': 'commit' - } - ''' + }""" r = None try: action = data['cmd'] @@ -1193,7 +1191,7 @@ class DhcpLever(SecureApiView): r = dhcp_expiry(data['client_ip']) elif action == 'release': r = dhcp_release(data['client_ip']) - except mydefs.LogicError as e: + except lib.LogicError as e: print('LogicError', e) r = str(e) return r diff --git a/accounts_app/views.py b/accounts_app/views.py index d0bc04f..6cdd24e 100644 --- a/accounts_app/views.py +++ b/accounts_app/views.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from django.contrib.auth.decorators import login_required from django.contrib.auth import authenticate, login, logout from django.core.exceptions import PermissionDenied @@ -15,7 +14,7 @@ from group_app.models import Group from .models import UserProfile from .forms import AvatarChangeForm -import mydefs +from djing import lib from guardian.decorators import permission_required_or_403 as permission_required from guardian.shortcuts import get_objects_for_user, assign_perm, remove_perm @@ -62,9 +61,9 @@ class SignOut(RedirectView): @login_required -@mydefs.only_admins +@lib.only_admins def profile_show(request, uid=0): - uid = mydefs.safe_int(uid) + uid = lib.safe_int(uid) if uid == 0: return redirect('acc_app:other_profile', uid=request.user.id) @@ -87,7 +86,7 @@ def profile_show(request, uid=0): }) -@method_decorator((login_required, mydefs.only_admins), name='dispatch') +@method_decorator((login_required, lib.only_admins), name='dispatch') class AvatarUpdateView(UpdateView): form_class = AvatarChangeForm template_name = 'accounts/settings/ch_info.html' @@ -100,7 +99,7 @@ class AvatarUpdateView(UpdateView): @login_required -@mydefs.only_admins +@lib.only_admins def ch_info(request): if request.method == 'POST': user = request.user @@ -169,7 +168,7 @@ def create_profile(request): @login_required -@mydefs.only_admins +@lib.only_admins def delete_profile(request, uid): prf = get_object_or_404(UserProfile, id=uid) if uid != request.user.id: @@ -180,7 +179,7 @@ def delete_profile(request, uid): return redirect('acc_app:accounts_list') -@method_decorator((login_required, mydefs.only_admins), name='dispatch') +@method_decorator((login_required, lib.only_admins), name='dispatch') class AccountsListView(BaseAccListView): template_name = 'accounts/acc_list.html' context_object_name = 'users' @@ -288,7 +287,7 @@ def set_abon_groups_permission(request, uid): }) -@method_decorator((login_required, mydefs.only_admins), name='dispatch') +@method_decorator((login_required, lib.only_admins), name='dispatch') class ManageResponsibilityGroups(ListView): http_method_names = ('get', 'post') template_name = 'accounts/manage_responsibility_groups.html' diff --git a/agent/mod_mikrotik.py b/agent/mod_mikrotik.py index 03adb24..3f246ce 100644 --- a/agent/mod_mikrotik.py +++ b/agent/mod_mikrotik.py @@ -6,7 +6,7 @@ from abc import ABCMeta from hashlib import md5 from typing import Iterable, Optional, Tuple from .core import BaseTransmitter, NasFailedResult, NasNetworkError -from mydefs import singleton +from djing.lib import singleton from .structs import TariffStruct, AbonStruct, IpStruct, VectorAbon, VectorTariff from . import settings as local_settings from django.conf import settings diff --git a/agent/structs.py b/agent/structs.py index 13f85b2..39c7bac 100644 --- a/agent/structs.py +++ b/agent/structs.py @@ -1,18 +1,18 @@ # -*- coding: utf-8 -*- from abc import ABCMeta, abstractmethod from struct import pack, unpack, calcsize -from typing import Iterable -from .utils import int2ip, ip2int +from typing import Iterable, Optional +from djing.lib import int2ip, ip2int class BaseStruct(object, metaclass=ABCMeta): @abstractmethod - def serialize(self): - """привращаем инфу в бинарную строку""" + def serialize(self) -> Optional[bytes]: + """make binary""" @abstractmethod - def deserialize(self, data, *args): - """создаём объект из бинарной строки""" + def deserialize(self, data: bytes, *args): + """restore from binary""" def __ne__(self, other): return not self == other @@ -25,11 +25,11 @@ class IpStruct(BaseStruct): else: self.__ip = ip2int(str(ip)) - def serialize(self): + def serialize(self) -> Optional[bytes]: dt = pack("!I", int(self.__ip)) return dt - def deserialize(self, data, *args): + def deserialize(self, data: bytes, *args): dt = unpack("!I", data) self.__ip = int(dt[0]) return self @@ -54,12 +54,12 @@ class IpStruct(BaseStruct): # Как обслуживается абонент class TariffStruct(BaseStruct): - def __init__(self, tariff_id=0, speedIn=None, speedOut=None): + def __init__(self, tariff_id=0, speed_in=None, speed_out=None): self.tid = int(tariff_id) - self.speedIn = float(speedIn if speedIn is not None else 0.001) - self.speedOut = float(speedOut if speedOut is not None else 0.001) + self.speedIn = speed_in or 0 + self.speedOut = speed_out or 0 - def serialize(self): + def serialize(self) -> Optional[bytes]: dt = pack("!Iff", int(self.tid), float(self.speedIn), float(self.speedOut)) return dt @@ -67,7 +67,7 @@ class TariffStruct(BaseStruct): def is_empty(self): return self.tid == 0 and self.speedIn == 0.001 and self.speedOut == 0.001 - def deserialize(self, data, *args): + def deserialize(self, data: bytes, *args): dt = unpack("!Iff", data) self.tid = int(dt[0]) self.speedIn = float(dt[1]) @@ -96,7 +96,7 @@ class AbonStruct(BaseStruct): self.tariff = tariff self.is_active = is_active - def serialize(self): + def serialize(self) -> Optional[bytes]: if self.tariff is None: return if not isinstance(self.tariff, TariffStruct): @@ -106,7 +106,7 @@ class AbonStruct(BaseStruct): dt = pack("!LII?", self.uid, int(self.ip), self.tariff.tid, self.is_active) return dt - def deserialize(self, data, tariff=None): + def deserialize(self, data: bytes, tariff=None): dt = unpack("!LII?", data) self.uid = dt[0] self.ip = IpStruct(dt[1]) @@ -137,12 +137,12 @@ class ShapeItem(BaseStruct): self.abon = abon self.sid = sid - def serialize(self): + def serialize(self) -> Optional[bytes]: abon_pack = self.abon.serialize() dt = pack('!L', self.sid) return dt + abon_pack - def deserialize(self, data, *args): + def deserialize(self, data: bytes, *args): sz = calcsize('!L') dt = unpack('!L', data[:sz]) self.sid = dt diff --git a/clientsideapp/views.py b/clientsideapp/views.py index f95b796..68f9cfe 100644 --- a/clientsideapp/views.py +++ b/clientsideapp/views.py @@ -8,7 +8,7 @@ from django.utils.translation import gettext_lazy as _, gettext from abonapp.models import AbonLog, InvoiceForPayment, Abon from tariff_app.models import Tariff from taskapp.models import Task -from mydefs import LogicError +from djing.lib import LogicError from agent import NasFailedResult, NasNetworkError diff --git a/devapp/migrations/0001_initial.py b/devapp/migrations/0001_initial.py index fa24063..9103f5f 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 -import mydefs +from djing.lib import MyGenericIPAddressField class Migration(migrations.Migration): @@ -21,7 +21,7 @@ class Migration(migrations.Migration): fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ( - 'ip_address', mydefs.MyGenericIPAddressField(max_length=8, protocol='ipv4', verbose_name='Ip address')), + 'ip_address', MyGenericIPAddressField(max_length=8, protocol='ipv4', verbose_name='Ip address')), ('mac_addr', djing.fields.MACAddressField(blank=True, integer=True, null=True, unique=True, verbose_name='Mac address')), ('comment', models.CharField(max_length=256, verbose_name='Comment')), diff --git a/devapp/migrations/0002_auto_20180409_1318.py b/devapp/migrations/0002_auto_20180409_1318.py index ea0fad0..891cd0c 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 -import mydefs +from djing.lib import MyGenericIPAddressField class Migration(migrations.Migration): @@ -20,7 +20,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='device', name='ip_address', - field=mydefs.MyGenericIPAddressField(blank=True, max_length=8, null=True, protocol='ipv4', + field=MyGenericIPAddressField(blank=True, max_length=8, null=True, protocol='ipv4', verbose_name='Ip address'), ), ] diff --git a/dialing_app/views.py b/dialing_app/views.py index ce5c203..0108d26 100644 --- a/dialing_app/views.py +++ b/dialing_app/views.py @@ -16,7 +16,7 @@ from jsonview.decorators import json_view from abonapp.models import Abon from djing.global_base_views import SecureApiView from djing import JSONType -from mydefs import only_admins, safe_int +from djing.lib import only_admins, safe_int from .models import AsteriskCDR, SMSModel, SMSOut from .forms import SMSOutForm diff --git a/djing/lib/__init__.py b/djing/lib/__init__.py new file mode 100644 index 0000000..948f5d5 --- /dev/null +++ b/djing/lib/__init__.py @@ -0,0 +1,186 @@ +import socket +import struct +from datetime import timedelta +from collections import Iterator +from functools import wraps +from json import dumps +from django.http import HttpResponse, Http404, HttpResponseRedirect +from django.shortcuts import redirect +from django.db import models +from django.conf import settings + +DEBUG = getattr(settings, 'DEBUG', False) + + +def ip2int(addr): + try: + return struct.unpack("!I", socket.inet_aton(addr))[0] + except: + return 0 + + +def int2ip(addr): + try: + return socket.inet_ntoa(struct.pack("!I", addr)) + except: + return '' + + +def safe_float(fl): + try: + return 0.0 if fl is None or fl == '' else float(fl) + except ValueError: + return 0.0 + + +def safe_int(i): + try: + return 0 if i is None or i == '' else int(i) + except ValueError: + return 0 + + +def res_success(request, redirect_to='/'): + if request.is_ajax(): + return HttpResponse(dumps({'errnum': 0})) + else: + return redirect(redirect_to) + + +def res_error(request, text): + if request.is_ajax(): + return HttpResponse(dumps({'errnum': 1, 'errtext': text})) + else: + raise Http404(text) + + +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 чтоб можно было передавать классы вместо просто описания поля, +# классы передавать для того чтоб по значению кода из базы понять какой класс нужно взять для нужной функциональности. +# Например по коду в базе вам нужно определять как считать тариф абонента, что реализовано в возвращаемом классе. +class MyChoicesAdapter(Iterator): + chs = tuple() + current_index = 0 + _max_index = 0 + + # На вход принимает кортеж кортежей, вложенный из 2х элементов: кода и класса, как: TARIFF_CHOICES + def __init__(self, choices): + self._max_index = len(choices) + self.chs = choices + + def __next__(self): + if self.current_index >= self._max_index: + raise StopIteration + else: + e = self.chs + ci = self.current_index + res = e[ci][0], e[ci][1].description() + self.current_index += 1 + return res + + +# Allow to view only admins +def only_admins(fn): + @wraps(fn) + def wrapped(request, *args, **kwargs): + if request.user.is_admin: + return fn(request, *args, **kwargs) + else: + return redirect('client_side:home') + + return wrapped + + +# Russian localized timedelta +class RuTimedelta(timedelta): + def __new__(cls, tm): + if isinstance(tm, timedelta): + return timedelta.__new__( + cls, + days=tm.days, + seconds=tm.seconds, + microseconds=tm.microseconds + ) + + def __str__(self): + # hours, remainder = divmod(self.seconds, 3600) + # minutes, seconds = divmod(remainder, 60) + # text_date = "%d:%d" % ( + # hours, + # minutes + # ) + if self.days > 1: + ru_days = 'дней' + if 5 > self.days > 1: + ru_days = 'дня' + elif self.days == 1: + ru_days = 'день' + # text_date = '%d %s %s' % (self.days, ru_days, text_date) + text_date = '%d %s' % (self.days, ru_days) + else: + text_date = '' + return text_date + + +def require_ssl(view): + """ + Decorator that requires an SSL connection. If the current connection is not SSL, we redirect to the SSL version of + the page. + from: https://gist.github.com/ckinsey/9709984 + """ + + @wraps(view) + def wrapper(request, *args, **kwargs): + if not DEBUG and not request.is_secure(): + target_url = "https://" + request.META['HTTP_HOST'] + request.path_info + return HttpResponseRedirect(target_url) + return view(request, *args, **kwargs) + + return wrapper + + +class MultipleException(Exception): + def __init__(self, err_list): + if not isinstance(err_list, (list, tuple)): + raise TypeError + self.err_list = err_list + + +class LogicError(Exception): + pass + + +def singleton(class_): + instances = {} + + def getinstance(*args, **kwargs): + if class_ not in instances: + instances[class_] = class_(*args, **kwargs) + return instances[class_] + + return getinstance diff --git a/djing/settings.py b/djing/settings.py index 4b38677..d73a627 100644 --- a/djing/settings.py +++ b/djing/settings.py @@ -25,7 +25,7 @@ ALLOWED_HOSTS = local_settings.ALLOWED_HOSTS # required for django-guardian AUTHENTICATION_BACKENDS = ( - 'djing.auth_backends.CustomAuthBackend', + 'djing.lib.auth_backends.CustomAuthBackend', # 'django.contrib.auth.backends.ModelBackend', # default 'guardian.backends.ObjectPermissionBackend' ) diff --git a/docs/dev.md b/docs/dev.md index 09d00e4..ce2fff9 100644 --- a/docs/dev.md +++ b/docs/dev.md @@ -343,7 +343,7 @@ def check_news(request): return HttpResponse(dumps(r)) ``` -Убедитесь что вашему представлению не будет доступа от абонентов, об этом позаботится декоратор *only_admins* из *mydefs*. *mydefs* лежит в корне проекта. +Убедитесь что вашему представлению не будет доступа от абонентов, об этом позаботится декоратор *only_admins* из *djing.lib*. После получения сообщения надо вернуть словарь с параметрами: diff --git a/mapapp/views.py b/mapapp/views.py index f996f42..1b61c28 100644 --- a/mapapp/views.py +++ b/mapapp/views.py @@ -14,7 +14,7 @@ from jsonview.decorators import json_view from group_app.models import Group from .models import Dot from .forms import DotForm -from mydefs import safe_int +from djing.lib import safe_int from devapp.models import Device from guardian.decorators import permission_required diff --git a/periodic.py b/periodic.py index 6d7b4cc..0657bb5 100755 --- a/periodic.py +++ b/periodic.py @@ -9,7 +9,7 @@ from django.db import transaction from django.db.models import signals from abonapp.models import Abon, AbonTariff, abontariff_pre_delete, PeriodicPayForId, AbonLog from agent import Transmitter, NasNetworkError, NasFailedResult -from mydefs import LogicError +from djing.lib import LogicError def main(): diff --git a/statistics/migrations/0001_initial.py b/statistics/migrations/0001_initial.py index bd83354..4148c97 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 -import mydefs +from djing.lib import MyGenericIPAddressField import statistics.fields @@ -18,8 +18,7 @@ class Migration(migrations.Migration): name='StatCache', fields=[ ('last_time', statistics.fields.UnixDateTimeField()), - ( - 'ip', mydefs.MyGenericIPAddressField(max_length=8, primary_key=True, protocol='ipv4', serialize=False)), + ('ip', MyGenericIPAddressField(max_length=8, primary_key=True, protocol='ipv4', serialize=False)), ('octets', models.PositiveIntegerField(default=0)), ('packets', models.PositiveIntegerField(default=0)), ], diff --git a/statistics/models.py b/statistics/models.py index c5bc191..13ff2b4 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 mydefs import MyGenericIPAddressField +from djing.lib import MyGenericIPAddressField from .fields import UnixDateTimeField diff --git a/tariff_app/base_intr.py b/tariff_app/base_intr.py index cd2f8aa..970dbe9 100644 --- a/tariff_app/base_intr.py +++ b/tariff_app/base_intr.py @@ -17,7 +17,7 @@ class TariffBase(metaclass=ABCMeta): @staticmethod def description() -> AnyStr: """ - Usage in mydefs.MyChoicesAdapter for choices fields. + Usage in djing.lib.MyChoicesAdapter for choices fields. :return: human readable description """ raise NotImplementedError @@ -54,5 +54,5 @@ class PeriodicPayCalcBase(metaclass=ABCMeta): @staticmethod def description() -> AnyStr: """Return text description. - Uses in mydefs.MyChoicesAdapter for CHOICES fields""" + Uses in djing.lib.MyChoicesAdapter for CHOICES fields""" raise NotImplementedError diff --git a/tariff_app/models.py b/tariff_app/models.py index 3cb82cb..6ca5e04 100644 --- a/tariff_app/models.py +++ b/tariff_app/models.py @@ -5,7 +5,7 @@ from django.dispatch import receiver from .base_intr import TariffBase, PeriodicPayCalcBase from .custom_tariffs import TARIFF_CHOICES, PERIODIC_PAY_CHOICES from group_app.models import Group -from mydefs import MyChoicesAdapter +from djing.lib import MyChoicesAdapter from jsonfield import JSONField diff --git a/tariff_app/views.py b/tariff_app/views.py index 9dbdaaa..1f0d7d9 100644 --- a/tariff_app/views.py +++ b/tariff_app/views.py @@ -12,7 +12,7 @@ from guardian.decorators import permission_required_or_403 as permission_require from djing.global_base_views import OrderingMixin from .models import Tariff, PeriodicPay -import mydefs +from djing import lib from . import forms @@ -21,7 +21,7 @@ class BaseServiceListView(ListView): paginate_by = getattr(settings, 'PAGINATION_ITEMS_PER_PAGE', 10) -@method_decorator((login_required, mydefs.only_admins), name='dispatch') +@method_decorator((login_required, lib.only_admins), name='dispatch') class TariffsListView(BaseServiceListView, OrderingMixin): """ Show Services(Tariffs) list @@ -33,7 +33,7 @@ class TariffsListView(BaseServiceListView, OrderingMixin): @login_required def edit_tarif(request, tarif_id=0): - tarif_id = mydefs.safe_int(tarif_id) + tarif_id = lib.safe_int(tarif_id) if tarif_id == 0: if not request.user.has_perm('tariff_app.add_tariff'): @@ -70,7 +70,7 @@ def del_tarif(request, tid): messages.success(request, _('Service has been deleted')) else: messages.error(request, _('Not have a confirmations of delete')) - return mydefs.res_success(request, 'tarifs:home') + return lib.res_success(request, 'tarifs:home') return render_to_text('tariff_app/modal_del_warning.html', {'tid': tid}, request=request) diff --git a/taskapp/handle.py b/taskapp/handle.py index c973dc3..cabcdfc 100644 --- a/taskapp/handle.py +++ b/taskapp/handle.py @@ -2,7 +2,7 @@ from django.utils.translation import gettext as _ from chatbot.telebot import send_notify from chatbot.models import ChatException -from mydefs import MultipleException +from djing.lib import MultipleException class TaskException(Exception): diff --git a/taskapp/migrations/0001_initial.py b/taskapp/migrations/0001_initial.py index 06da89c..fe69535 100644 --- a/taskapp/migrations/0001_initial.py +++ b/taskapp/migrations/0001_initial.py @@ -51,7 +51,7 @@ class Migration(migrations.Migration): ('priority', models.CharField(choices=[('A', 'Higher'), ('C', 'Average'), ('E', 'Low')], default='E', max_length=1, verbose_name='A priority')), - ('out_date', models.DateField(blank=True, default=taskapp.models._delta_add_days, null=True, + ('out_date', models.DateField(blank=True, default=taskapp.models.delta_add_days, null=True, verbose_name='Reality')), ('time_of_create', models.DateTimeField(auto_now_add=True, verbose_name='Date of create')), ('state', models.CharField(choices=[('S', 'New'), ('C', 'Confused'), ('F', 'Completed')], default='S', diff --git a/taskapp/views.py b/taskapp/views.py index 949e521..b0624a8 100644 --- a/taskapp/views.py +++ b/taskapp/views.py @@ -1,5 +1,3 @@ -# coding=utf-8 -from json import dumps from django.contrib.auth.decorators import login_required from django.core.exceptions import PermissionDenied from django.http import HttpResponse @@ -15,12 +13,14 @@ from datetime import datetime from django.views.generic.edit import FormMixin, DeleteView, UpdateView from guardian.decorators import permission_required_or_403 as permission_required +from jsonview.decorators import json_view + from chatbot.models import MessageQueue from abonapp.models import Abon from djing import httpresponse_to_referrer +from djing.lib import only_admins, safe_int, MultipleException, RuTimedelta from .handle import TaskException from .models import Task, ExtraComment -from mydefs import only_admins, safe_int, MultipleException, RuTimedelta from .forms import TaskFrm, ExtraCommentForm @@ -249,6 +249,7 @@ def remind(request, task_id): return redirect('taskapp:home') +@json_view def check_news(request): if request.user.is_authenticated and request.user.is_admin: msg = MessageQueue.objects.pop(user=request.user, tag='taskap') From f7008d2d0cccadb7313ab3819f2975c8615be4c1 Mon Sep 17 00:00:00 2001 From: bashmak Date: Thu, 31 May 2018 14:53:47 +0300 Subject: [PATCH 3/5] Create monitoring for ZTE devices --- devapp/dev_types.py | 68 +++- devapp/migrations/0003_auto_20180529_1311.py | 60 +++ devapp/models.py | 8 +- devapp/templates/devapp/add_dev.html | 2 +- .../custom_dev_page/olt_ztec320_ports.html | 2 +- .../templates/devapp/custom_dev_page/onu.html | 19 +- .../devapp/custom_dev_page/onu_for_zte.html | 91 +++++ devapp/templates/devapp/dev.html | 2 +- devapp/views.py | 16 +- djing/{ => lib}/auth_backends.py | 0 migrate_to_0.2.py | 359 ------------------ mydefs.py | 187 --------- static/css/custom.css | 18 +- 13 files changed, 243 insertions(+), 589 deletions(-) create mode 100644 devapp/migrations/0003_auto_20180529_1311.py create mode 100644 devapp/templates/devapp/custom_dev_page/onu_for_zte.html rename djing/{ => lib}/auth_backends.py (100%) delete mode 100755 migrate_to_0.2.py delete mode 100644 mydefs.py diff --git a/devapp/dev_types.py b/devapp/dev_types.py index 7dda24e..c6c46c7 100644 --- a/devapp/dev_types.py +++ b/devapp/dev_types.py @@ -1,9 +1,9 @@ -from typing import AnyStr, Iterable +from typing import AnyStr, Iterable, Optional, Dict from datetime import timedelta from easysnmp import EasySNMPTimeoutError from django.utils.translation import gettext_lazy as _, gettext -from mydefs import RuTimedelta, safe_int +from djing.lib import RuTimedelta, safe_int from .base_intr import DevBase, SNMPBaseWorker, BasePort, DeviceImplementationError, ListOrError @@ -197,7 +197,7 @@ class OnuDevice(DevBase, SNMPBaseWorker): def get_details(self): if self.db_instance is None: return - num = self.db_instance.snmp_item_num + num = safe_int(self.db_instance.snmp_extra) if num == 0: return try: @@ -272,8 +272,17 @@ class EltexSwitch(DLinkDevice): return False -class Olt_ZTE_C320(OLTDevice): +def conv_signal(lvl: int) -> float: + if lvl == 65535: return 0.0 + r = 0 + if 0 < lvl < 30000: + r = lvl * 0.002 - 30 + elif 60000 < lvl < 65534: + r = (lvl - 65534) * 0.002 - 30 + return round(r, 2) + +class Olt_ZTE_C320(OLTDevice): @staticmethod def description(): return gettext('OLT ZTE C320') @@ -287,16 +296,8 @@ class Olt_ZTE_C320(OLTDevice): return fibers def get_ports_on_fiber(self, fiber_num: int) -> Iterable: - def conv_signal(lvl: int) -> float: - if lvl == 65535: return 0.0 - r = 0 - if 0 < lvl < 30000: - r = lvl * 0.002 - 30 - elif 60000 < lvl < 65534: - r = (lvl - 65534) * 0.002 - 30 - return round(r, 2) - - onu_types = self.get_list('.1.3.6.1.4.1.3902.1012.3.28.1.1.1.%d' % fiber_num) + + onu_types = self.get_list_keyval('.1.3.6.1.4.1.3902.1012.3.28.1.1.1.%d' % fiber_num) onu_ports = self.get_list('.1.3.6.1.4.1.3902.1012.3.28.1.1.2.%d' % fiber_num) onu_signals = self.get_list('.1.3.6.1.4.1.3902.1012.3.50.12.1.1.10.%d' % fiber_num) @@ -304,13 +305,15 @@ class Olt_ZTE_C320(OLTDevice): onu_sns = self.get_list('.1.3.6.1.4.1.3902.1012.3.28.1.1.5.%d' % fiber_num) onu_prefixs = self.get_list('.1.3.6.1.4.1.3902.1012.3.50.11.2.1.1.%d' % fiber_num) onu_list = ({ - 'onu_type': onu_type, + 'onu_type': onu_type_num[0], 'onu_port': onu_port, 'onu_signal': conv_signal(safe_int(onu_signal)), - 'onu_sn': onu_prefix + ''.join('%.2X' % ord(i) for i in onu_sn[-4:]), # Real sn in last 4 octets - } for onu_type, onu_port, onu_signal, onu_sn, onu_prefix in zip( + 'onu_sn': onu_prefix + ''.join('%.2X' % ord(i) for i in onu_sn[-4:]), # Real sn in last 4 octets, + 'snmp_extra': "%d.%d" % (fiber_num, safe_int(onu_type_num[1])), + } for onu_type_num, onu_port, onu_signal, onu_sn, onu_prefix in zip( onu_types, onu_ports, onu_signals, onu_sns, onu_prefixs )) + return onu_list def uptime(self): @@ -326,3 +329,34 @@ class Olt_ZTE_C320(OLTDevice): def get_template_name(self): return 'olt_ztec320.html' + + +class ZteOnuDevice(OnuDevice): + @staticmethod + def description(): + return _('ZTE PON ONU') + + def get_details(self) -> Optional[Dict]: + if self.db_instance is None: + return + snmp_extra = self.db_instance.snmp_extra + if not snmp_extra: + return + try: + fiber_num, onu_num = snmp_extra.split('.') + fiber_num, onu_num = int(fiber_num), int(onu_num) + status = self.get_item('.1.3.6.1.4.1.3902.1012.3.50.12.1.1.1.%d.%d.1' % (fiber_num, onu_num)) + signal = self.get_item('.1.3.6.1.4.1.3902.1012.3.50.12.1.1.10.%d.%d.1' % (fiber_num, onu_num)) + distance = self.get_item('.1.3.6.1.4.1.3902.1012.3.50.12.1.1.18.%d.%d.1' % (fiber_num, onu_num)) + name = self.get_item('.1.3.6.1.4.1.3902.1012.3.50.11.2.1.1.%d.%d' % (fiber_num, onu_num)) + return { + 'status': status, + 'signal': conv_signal(safe_int(signal)), + 'name': name, + 'distance': int(distance) / 10 if distance != 'NOSUCHINSTANCE' else 0 + } + except ValueError: + pass + + def get_template_name(self): + return 'onu_for_zte.html' diff --git a/devapp/migrations/0003_auto_20180529_1311.py b/devapp/migrations/0003_auto_20180529_1311.py new file mode 100644 index 0000000..747c845 --- /dev/null +++ b/devapp/migrations/0003_auto_20180529_1311.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2018-05-29 13:11 +from __future__ import unicode_literals +import os +from json import load + +from django.db import migrations, models +from django.core import serializers + +TMP_FILE = '/tmp/djing_snmp_info_backup.json' + + +def snmp_backup_info(apps, _): + Device = apps.get_model('devapp', 'Device') + obs = Device.objects.only('snmp_item_num') + with open(TMP_FILE, 'w') as f: + serializers.serialize('json', obs, stream=f) + + +def snmp_restore_info_to_new_scheme(apps, _): + Device = apps.get_model('devapp', 'Device') + with open(TMP_FILE, 'r') as f: + for device in load(f): + Device.objects.filter(pk=device['pk']).update(snmp_extra=device['fields']['snmp_item_num']) + if os.path.isfile(TMP_FILE): + os.remove(TMP_FILE) + + +class Migration(migrations.Migration): + + dependencies = [ + ('devapp', '0002_auto_20180409_1318'), + ] + + operations = [ + migrations.RunPython(snmp_backup_info), + migrations.AlterModelOptions( + name='device', + options={'ordering': ('id',), 'permissions': (('can_view_device', 'Can view device'),), 'verbose_name': 'Device', 'verbose_name_plural': 'Devices'}, + ), + migrations.AlterModelOptions( + name='port', + options={'ordering': ('num',), 'permissions': (('can_toggle_ports', 'Can toggle ports'),), 'verbose_name': 'Port', 'verbose_name_plural': 'Ports'}, + ), + migrations.RemoveField( + model_name='device', + name='snmp_item_num', + ), + migrations.AddField( + model_name='device', + name='snmp_extra', + field=models.CharField(blank=True, max_length=256, null=True, verbose_name='SNMP extra info'), + ), + migrations.AlterField( + model_name='device', + name='devtype', + field=models.CharField(choices=[('Dl', 'DLink switch'), ('Pn', 'PON OLT'), ('On', 'PON ONU'), ('Ex', 'Eltex switch'), ('Zt', 'OLT ZTE C320'), ('Zo', 'ZTE PON ONU')], default='Dl', max_length=2, verbose_name='Device type'), + ), + migrations.RunPython(snmp_restore_info_to_new_scheme) + ] diff --git a/devapp/models.py b/devapp/models.py index abf87c5..24e5066 100644 --- a/devapp/models.py +++ b/devapp/models.py @@ -1,8 +1,9 @@ import os from django.db import models + from djing.fields import MACAddressField from .base_intr import DevBase -from mydefs import MyGenericIPAddressField, MyChoicesAdapter +from djing.lib import MyGenericIPAddressField, MyChoicesAdapter from . import dev_types from subprocess import run from django.conf import settings @@ -27,7 +28,8 @@ class Device(models.Model): ('Pn', dev_types.OLTDevice), ('On', dev_types.OnuDevice), ('Ex', dev_types.EltexSwitch), - ('Zt', dev_types.Olt_ZTE_C320) + ('Zt', dev_types.Olt_ZTE_C320), + ('Zo', dev_types.ZteOnuDevice) ) devtype = models.CharField(_('Device type'), max_length=2, default=DEVICE_TYPES[0][0], choices=MyChoicesAdapter(DEVICE_TYPES)) @@ -36,7 +38,7 @@ class Device(models.Model): parent_dev = models.ForeignKey('self', verbose_name=_('Parent device'), blank=True, null=True, on_delete=models.SET_NULL) - snmp_item_num = models.PositiveSmallIntegerField(_('SNMP Number'), default=0, blank=True) + snmp_extra = models.CharField(_('SNMP extra info'), max_length=256, null=True, blank=True) NETWORK_STATES = ( ('und', _('Undefined')), diff --git a/devapp/templates/devapp/add_dev.html b/devapp/templates/devapp/add_dev.html index 793f829..30fbb9c 100644 --- a/devapp/templates/devapp/add_dev.html +++ b/devapp/templates/devapp/add_dev.html @@ -63,7 +63,7 @@ {% bootstrap_icon 'list-alt' as ic %} - {% bootstrap_field form.snmp_item_num addon_before=ic %} + {% bootstrap_field form.snmp_extra addon_before=ic %} {% bootstrap_field form.is_noticeable %} diff --git a/devapp/templates/devapp/custom_dev_page/olt_ztec320_ports.html b/devapp/templates/devapp/custom_dev_page/olt_ztec320_ports.html index 19c6db0..adfaf39 100644 --- a/devapp/templates/devapp/custom_dev_page/olt_ztec320_ports.html +++ b/devapp/templates/devapp/custom_dev_page/olt_ztec320_ports.html @@ -42,7 +42,7 @@ {{ onu.onu_signal }} {{ onu.onu_sn }} - + diff --git a/devapp/templates/devapp/custom_dev_page/onu.html b/devapp/templates/devapp/custom_dev_page/onu.html index 6e93877..ae164fe 100644 --- a/devapp/templates/devapp/custom_dev_page/onu.html +++ b/devapp/templates/devapp/custom_dev_page/onu.html @@ -31,9 +31,10 @@ {% if dev.parent_dev %}
  • {% with pdev=dev.parent_dev pdgrp=dev.parent_dev.group %} - {% trans 'Parent device' %}:{{ pdev.ip_address|default:'-' }} {{ pdev.comment }} + {% trans 'Parent device' %}: + + {{ pdev.ip_address|default:'-' }} {{ pdev.comment }} + {% endwith %}
  • {% endif %} @@ -51,20 +52,20 @@ {% if onu_details %} {% if onu_details.err %}
    -
    +
    {% trans 'ONU error' %}: {{ onu_details.err }}
    {% else %}
    -
    +
    {% if onu_details.status == '3' %} - + {% elif onu_details.status == '2' %} - + {% else %} - + {% endif %}
    @@ -76,7 +77,7 @@ {% trans 'Mac on OLT' %}: {{ onu_details.mac }}

    - + diff --git a/devapp/templates/devapp/custom_dev_page/onu_for_zte.html b/devapp/templates/devapp/custom_dev_page/onu_for_zte.html new file mode 100644 index 0000000..49317c6 --- /dev/null +++ b/devapp/templates/devapp/custom_dev_page/onu_for_zte.html @@ -0,0 +1,91 @@ +{% extends request.is_ajax|yesno:'nullcont.htm,devapp/ext.htm' %} +{% load i18n %} +{% block content %} + +{% with uptime=dev_manager.uptime onu_details=dev_manager.get_details %} +
    +
    +
    +
    +
    {{ dev.get_devtype_display|default:_('Title of the type of switch') }}. + {% if uptime %} + {% trans 'Uptime' %} {{ uptime }} + {% endif %} +
    +
    +
    +
      +
    • {% trans 'Ip address' %}: {{ dev.ip_address|default:'-' }}
    • +
    • {% trans 'Mac' %}: {{ dev.mac_addr }}
    • +
    • {% trans 'Description' %}: {{ dev.comment }}
    • + {% for da in dev_accs %} +
    • {% trans 'Attached user' %}: + {% if da.group %} + {{ da.get_full_name }} + {% else %} + {{ da.get_full_name }} + {% endif %} +
    • + {% endfor %} + {% if dev.parent_dev %} +
    • + {% with pdev=dev.parent_dev pdgrp=dev.parent_dev.group %} + {% trans 'Parent device' %}: + + {{ pdev.ip_address|default:'-' }} {{ pdev.comment }} + + {% endwith %} +
    • + {% endif %} +
    +
    +
    +
    +
    +
    +
    +

    {% trans 'ONU Status' %}

    +
    + +
    + {% if onu_details %} + {% if onu_details.err %} +
    +
    +
    +
    + {% trans 'ONU error' %}: {{ onu_details.err }}
    +
    + {% else %} +
    +
    + {% if onu_details.status == '1' %} + + {% elif onu_details.status == '2' %} + + {% else %} + + {% endif %} +
    +
    + + {% trans 'Name on OLT' %}: {{ onu_details.name }}
    + {% trans 'Distance(m)' %}: {{ onu_details.distance }}
    + {% trans 'Signal' %}: {{ onu_details.signal }}
    + +
    +
    + {% endif %} + {% else %} +

    {% trans 'Info does not fetch' %}

    + {% endif %} +
    +
    +
    +
    + +{% endwith %} +{% endblock %} diff --git a/devapp/templates/devapp/dev.html b/devapp/templates/devapp/dev.html index 3910334..31f2f9d 100644 --- a/devapp/templates/devapp/dev.html +++ b/devapp/templates/devapp/dev.html @@ -52,7 +52,7 @@
    {% bootstrap_icon 'list-alt' as ic %} - {% bootstrap_field form.snmp_item_num addon_before=ic %} + {% bootstrap_field form.snmp_extra addon_before=ic %} {% bootstrap_field form.is_noticeable %} diff --git a/devapp/views.py b/devapp/views.py index 44afebb..5417e75 100644 --- a/devapp/views.py +++ b/devapp/views.py @@ -14,7 +14,7 @@ from easysnmp import EasySNMPTimeoutError, EasySNMPError from django.views.generic import DetailView from devapp.base_intr import DeviceImplementationError -from mydefs import res_success, res_error, only_admins, safe_int +from djing.lib import res_success, res_error, only_admins, safe_int from abonapp.models import Abon from group_app.models import Group from accounts_app.models import UserProfile @@ -137,7 +137,7 @@ def dev(request, group_id, device_id=0): 'comment': request.GET.get('c'), 'ip_address': request.GET.get('ip'), 'man_passw': getattr(settings, 'DEFAULT_SNMP_PASSWORD', ''), - 'snmp_item_num': request.GET.get('n') or 0 + 'snmp_extra': request.GET.get('n') or '' }) else: frm = DeviceForm(instance=devinst) @@ -505,8 +505,8 @@ def fix_onu(request): # convert bytes mac address to str presentation mac address real_mac = ':'.join('%x' % ord(i) for i in srcmac) if mac == real_mac: - onu.snmp_item_num = snmpnum - onu.save(update_fields=('snmp_item_num',)) + onu.snmp_extra = str(snmpnum) + onu.save(update_fields=('snmp_extra',)) status = 0 text = ' ' % _('Fixed') break @@ -619,11 +619,11 @@ class NagiosObjectsConfView(global_base_views.AuthenticatedOrHashAuthView): if device.devtype == 'On': if device.parent_dev: host_addr = device.parent_dev.ip_address - conf = self.templ_onu(host_name, host_addr, mac=mac_addr, snmp_item=device.snmp_item_num or None) + conf = self.templ_onu(host_name, host_addr, mac=mac_addr, snmp_item=device.snmp_extra or None) else: if device.ip_address: host_addr = device.ip_address - conf = self.templ_onu(host_name, host_addr, mac=mac_addr, snmp_item=device.snmp_item_num or None) + conf = self.templ_onu(host_name, host_addr, mac=mac_addr, snmp_item=device.snmp_extra or None) else: parent_host_name = norm_name("%d%s" % ( device.parent_dev.pk, translit(device.parent_dev.comment, language_code='ru', reversed=True) @@ -651,7 +651,7 @@ class NagiosObjectsConfView(global_base_views.AuthenticatedOrHashAuthView): return '\n'.join(i for i in r if i) @staticmethod - def templ_onu(host_name: str, host_addr: str, mac: Optional[str], snmp_item: int): + def templ_onu(host_name: str, host_addr: str, mac: Optional[str], snmp_item: str): if not host_addr: return r = ( @@ -659,7 +659,7 @@ class NagiosObjectsConfView(global_base_views.AuthenticatedOrHashAuthView): "\tuse device-onu", "\thost_name %s" % host_name, "\taddress %s" % host_addr, - "\t_snmp_item %d" % snmp_item if snmp_item is not None else '', + "\t_snmp_item %s" % snmp_item if snmp_item is not None else '', "\t_mac_addr %s" % mac if mac is not None else '', "}\n" ) diff --git a/djing/auth_backends.py b/djing/lib/auth_backends.py similarity index 100% rename from djing/auth_backends.py rename to djing/lib/auth_backends.py diff --git a/migrate_to_0.2.py b/migrate_to_0.2.py deleted file mode 100755 index a2edee8..0000000 --- a/migrate_to_0.2.py +++ /dev/null @@ -1,359 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -import os -import sys -import shutil -from json import dump -import django - -''' -Some permissions is not migrates, all admins is superuser -after migrate. -''' - -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djing.settings") -from django.db.models import fields as django_fields - - -def get_fixture_from_unchanget_model(model_name: str, model_class): - """ - Создаёт фикстуру если модели между версиями не изменились - :param model_name: str 'app_label.model_name' - :param model_class: Model модель для которой надо сделать фикстуру - :return: список словарей - """ - print(model_name) - - def get_fields(obj): - fields = dict() - for field in obj._meta.get_fields(): - if isinstance(field, - (django_fields.reverse_related.ManyToOneRel, django_fields.reverse_related.ManyToManyRel)): - continue - field_val = getattr(obj, field.name) - if field_val is None: - continue - if isinstance(field, django_fields.related.ManyToManyField): - fields[field.name] = [f.pk for f in field_val.all()] - elif isinstance(field, - (django_fields.related.ForeignKey, django.contrib.contenttypes.fields.GenericForeignKey)): - fields[field.name] = field_val.pk - elif isinstance(field, django_fields.FloatField): - fields[field.name] = float(field_val) - elif isinstance(field, django_fields.DateTimeField): - fields[field.name] = str(field_val) - elif isinstance(field, django_fields.AutoField): - continue - else: - fields[field.name] = field_val - return fields - - res = [{ - 'model': model_name, - 'pk': obj.pk, - 'fields': get_fields(obj) - } for obj in model_class.objects.all()] - return res - - -def dump_groups(): - from abonapp import models - print('group_app.group') - res = [{ - 'model': 'group_app.group', - 'pk': abon_group.pk, - 'fields': { - 'title': abon_group.title - } - } for abon_group in models.AbonGroup.objects.all()] - return res - - -def dump_abonapp(): - from abonapp import models - - res = get_fixture_from_unchanget_model('abonapp.abontariff', models.AbonTariff) - - res += get_fixture_from_unchanget_model('abonapp.abonstreet', models.AbonStreet) - - res += get_fixture_from_unchanget_model('abonapp.extrafieldsmodel', models.ExtraFieldsModel) - - # res += get_fixture_from_unchanget_model('abonapp.abonlog', models.AbonLog) - - print('abonapp.abon') - res += [{ - 'model': 'abonapp.abon', - 'pk': abon.pk, - 'fields': { - 'current_tariff': abon.current_tariff.pk if abon.current_tariff else None, - 'group': abon.group.pk if abon.group else None, - 'ballance': abon.ballance, - 'ip_address': abon.ip_address, - 'description': abon.description, - 'street': abon.street.pk if abon.street else None, - 'house': abon.house, - 'extra_fields': [a.pk for a in abon.extra_fields.all()], - 'device': abon.device.pk if abon.device else None, - 'dev_port': abon.dev_port if abon.dev_port else None, - 'is_dynamic_ip': abon.is_dynamic_ip, - 'markers': abon.markers - } - } for abon in models.Abon.objects.filter(is_admin=False)] - - res += get_fixture_from_unchanget_model('abonapp.passportinfo', models.PassportInfo) - - res += get_fixture_from_unchanget_model('abonapp.invoiceforpayment', models.InvoiceForPayment) - - res += get_fixture_from_unchanget_model('abonapp.alltimepaylog', models.AllTimePayLog) - - res += get_fixture_from_unchanget_model('abonapp.abonrawpassword', models.AbonRawPassword) - - res += get_fixture_from_unchanget_model('abonapp.additionaltelephone', models.AdditionalTelephone) - - res += get_fixture_from_unchanget_model('abonapp.periodicpayforid', models.PeriodicPayForId) - - return res - - -def dump_tariffs(): - from tariff_app import models - from abonapp.models import AbonGroup - print('tariff_app.tariff') - res = [{ - 'model': 'tariff_app.tariff', - 'pk': trf.pk, - 'fields': { - 'title': trf.title, - 'descr': trf.descr, - 'speedIn': trf.speedIn, - 'speedOut': trf.speedOut, - 'amount': trf.amount, - 'calc_type': trf.calc_type, - 'is_admin': trf.is_admin, - 'groups': [ag.pk for ag in AbonGroup.objects.filter(tariffs__in=[trf])] - } - } for trf in models.Tariff.objects.all()] - - res += get_fixture_from_unchanget_model('tariff_app.periodicpay', models.PeriodicPay) - - return res - - -def dump_devs(): - from devapp import models - print('devapp.device') - res = [{ - 'model': 'devapp.device', - 'pk': dv.pk, - 'fields': { - 'ip_address': dv.ip_address, - 'mac_addr': str(dv.mac_addr) if dv.mac_addr else None, - 'comment': dv.comment, - 'devtype': dv.devtype, - 'man_passw': dv.man_passw, - 'group': dv.user_group.pk if dv.user_group else None, - 'parent_dev': dv.parent_dev.pk if dv.parent_dev else None, - 'snmp_item_num': dv.snmp_item_num, - 'status': dv.status, - 'is_noticeable': dv.is_noticeable - } - } for dv in models.Device.objects.all()] - - res += get_fixture_from_unchanget_model('devapp.port', models.Port) - return res - - -def dump_accounts(): - from accounts_app import models - from abonapp.models import AbonGroup - - def get_responsibility_groups(account): - responsibility_groups = AbonGroup.objects.filter(profiles__in=[account]) - ids = [ag.pk for ag in responsibility_groups] - return ids - - print('accounts_app.baseaccount') - res = [{ - 'model': 'accounts_app.baseaccount', - 'pk': up.pk, - 'fields': { - 'username': up.username, - 'fio': up.fio, - 'birth_day': up.birth_day, - 'is_active': up.is_active, - 'is_admin': up.is_admin, - 'telephone': up.telephone, - 'password': up.password, - 'last_login': up.last_login, - 'is_superuser': up.is_admin - } - } for up in models.UserProfile.objects.all()] - - print('accounts_app.userprofile') - res += [{ - 'model': 'accounts_app.userprofile', - 'pk': up.pk, - 'fields': { - 'avatar': up.avatar.pk if up.avatar else None, - 'email': up.email, - 'responsibility_groups': get_responsibility_groups(up) - } - } for up in models.UserProfile.objects.filter(is_admin=True)] - - return res - - -def dump_photos(): - from photo_app.models import Photo - print('photo_app.photo') - res = [{ - 'model': 'photo_app.photo', - 'pk': p.pk, - 'fields': { - 'image': "%s" % p.image, - 'wdth': p.wdth, - 'heigt': p.heigt - } - } for p in Photo.objects.all()] - return res - - -def dump_chatbot(): - from chatbot import models - res = get_fixture_from_unchanget_model('chatbot.telegrambot', models.TelegramBot) - res += get_fixture_from_unchanget_model('chatbot.messagehistory', models.MessageHistory) - res += get_fixture_from_unchanget_model('chatbot.messagequeue', models.MessageQueue) - return res - - -def dump_map(): - from mapapp import models - res = get_fixture_from_unchanget_model('mapapp.dot', models.Dot) - return res - - -def dump_task_app(): - from taskapp import models - res = get_fixture_from_unchanget_model('taskapp.changelog', models.ChangeLog) - res += get_fixture_from_unchanget_model('taskapp.task', models.Task) - res += get_fixture_from_unchanget_model('taskapp.ExtraComment', models.ExtraComment) - return res - - -def dump_auth(): - from django.contrib.auth import models - from django.contrib.contenttypes.models import ContentType - res = get_fixture_from_unchanget_model('contenttypes.contenttype', ContentType) - res += get_fixture_from_unchanget_model('auth.group', models.Group) - res += get_fixture_from_unchanget_model('auth.permission', models.Permission) - return res - - -def dump_guardian(): - from guardian import models - print('guardian.groupobjectpermission') - res = [{ - 'model': 'guardian.groupobjectpermission', - 'pk': gp.pk, - 'fields': { - 'group': gp.group.pk, - 'permission': gp.permission.pk, - 'content_type': gp.content_type.pk, - 'object_pk': str(gp.object_pk), - 'content_object': gp.content_object.pk - } - } for gp in models.GroupObjectPermission.objects.all()] - print('guardian.userobjectpermission') - res += [{ - 'model': 'guardian.userobjectpermission', - 'pk': up.pk, - 'fields': { - 'permission': up.permission.pk, - 'content_type': up.content_type.pk, - 'object_pk': str(up.object_pk), - 'user': up.user.pk - } - } for up in models.UserObjectPermission.objects.all()] - return res - - -def make_migration(): - from datetime import datetime, date - - def my_date_converter(o): - if isinstance(o, datetime) or isinstance(o, date): - return "%s" % o - - def appdump(path, func): - fname = os.path.join(*path) - path_dir = os.path.join(*path[:-1]) - if not os.path.isdir(path_dir): - os.mkdir(path_dir) - with open(fname, 'w') as f: - dump(func(), f, default=my_date_converter, ensure_ascii=False) - - if not os.path.isdir('fixtures'): - os.mkdir('fixtures') - appdump(['fixtures', 'group_app', 'groups_fixture.json'], dump_groups) - appdump(['fixtures', 'tariff_app', 'tariffs_fixture.json'], dump_tariffs) - appdump(['fixtures', 'photo_app', 'photos_fixture.json'], dump_photos) - appdump(['fixtures', 'devapp', 'devs_fixture.json'], dump_devs) - appdump(['fixtures', 'accounts_app', 'accounts_fixture.json'], dump_accounts) - appdump(['fixtures', 'abonapp', 'abon_fixture.json'], dump_abonapp) - appdump(['fixtures', 'chatbot', 'chatbot_fixture.json'], dump_chatbot) - appdump(['fixtures', 'mapapp', 'map_fixture.json'], dump_map) - appdump(['fixtures', 'taskapp', 'task_fixture.json'], dump_task_app) - # appdump(['fixtures', 'accounts_app', 'auth_fixture.json'], dump_auth) - # appdump(['fixtures', 'accounts_app', 'guardian_fixture.json'], dump_guardian) - - -def move_to_fixtures_dirs(): - fixdir = 'fixtures' - for dr in os.listdir(fixdir): - fixture_dir = os.path.join(fixdir, dr) - for fixture_file in os.listdir(fixture_dir): - from_file = os.path.join(fixture_dir, fixture_file) - dst_dir = os.path.join(dr, fixdir) - to_file = os.path.join(dst_dir, fixture_file) - if not os.path.isdir(dst_dir): - os.mkdir(dst_dir) - shutil.copyfile(from_file, to_file) - print('cp %s -> %s' % (from_file, to_file)) - - -def apply_fixtures(): - from django.core.management import execute_from_command_line - from accounts_app.models import UserProfile - # from django.contrib.auth import models - - UserProfile.objects.filter(username='AnonymousUser').delete() - # print('clearing auth.group') - # models.Group.objects.all().delete() - # print('clearing auth.permission') - # models.Permission.objects.all().delete() - - fixtures_names = [ - 'groups_fixture.json', 'tariffs_fixture.json', 'photos_fixture.json', - 'devs_fixture.json', 'accounts_fixture.json', 'abon_fixture.json', - 'chatbot_fixture.json', 'map_fixture.json', 'task_fixture.json' - ] - # 'auth_fixture.json', 'guardian_fixture.json' - print('./manage.py loaddata ' + ', '.join(fixtures_names)) - execute_from_command_line([sys.argv[0], 'loaddata'] + fixtures_names) - - -if __name__ == '__main__': - if len(sys.argv) < 2: - print('Usage: ./migrate_to_0.2.py [makedump OR applydump]') - exit(1) - choice = sys.argv[1] - if choice == 'applydump': - django.setup() - move_to_fixtures_dirs() - apply_fixtures() - shutil.rmtree('fixtures') - elif choice == 'makedump': - django.setup() - make_migration() - else: - print('Unexpected choice') diff --git a/mydefs.py b/mydefs.py deleted file mode 100644 index d885fa0..0000000 --- a/mydefs.py +++ /dev/null @@ -1,187 +0,0 @@ -# -*- coding: utf-8 -*- -from datetime import timedelta -from json import dumps -import socket -import struct -from collections import Iterator -from functools import wraps -from django.http import HttpResponse, Http404, HttpResponseRedirect -from django.shortcuts import redirect -from django.db import models -from django.conf import settings - -DEBUG = getattr(settings, 'DEBUG', False) - - -def ip2int(addr): - try: - return struct.unpack("!I", socket.inet_aton(addr))[0] - except: - return 0 - - -def int2ip(addr): - try: - return socket.inet_ntoa(struct.pack("!I", addr)) - except: - return '' - - -def safe_float(fl): - try: - return 0.0 if fl is None or fl == '' else float(fl) - except ValueError: - return 0.0 - - -def safe_int(i): - try: - return 0 if i is None or i == '' else int(i) - except ValueError: - return 0 - - -def res_success(request, redirect_to='/'): - if request.is_ajax(): - return HttpResponse(dumps({'errnum': 0})) - else: - return redirect(redirect_to) - - -def res_error(request, text): - if request.is_ajax(): - return HttpResponse(dumps({'errnum': 1, 'errtext': text})) - else: - raise Http404(text) - - -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 чтоб можно было передавать классы вместо просто описания поля, -# классы передавать для того чтоб по значению кода из базы понять какой класс нужно взять для нужной функциональности. -# Например по коду в базе вам нужно определять как считать тариф абонента, что реализовано в возвращаемом классе. -class MyChoicesAdapter(Iterator): - chs = tuple() - current_index = 0 - _max_index = 0 - - # На вход принимает кортеж кортежей, вложенный из 2х элементов: кода и класса, как: TARIFF_CHOICES - def __init__(self, choices): - self._max_index = len(choices) - self.chs = choices - - def __next__(self): - if self.current_index >= self._max_index: - raise StopIteration - else: - e = self.chs - ci = self.current_index - res = e[ci][0], e[ci][1].description() - self.current_index += 1 - return res - - -# Декоратор проверяет аккаунт, чтоб не пускать клиентов в страницы администрации -def only_admins(fn): - @wraps(fn) - def wrapped(request, *args, **kwargs): - if request.user.is_admin: - return fn(request, *args, **kwargs) - else: - return redirect('client_side:home') - - return wrapped - - -# Русифицированный вывод timedelta -class RuTimedelta(timedelta): - def __new__(cls, tm): - if isinstance(tm, timedelta): - return timedelta.__new__( - cls, - days=tm.days, - seconds=tm.seconds, - microseconds=tm.microseconds - ) - - def __str__(self): - # hours, remainder = divmod(self.seconds, 3600) - # minutes, seconds = divmod(remainder, 60) - # text_date = "%d:%d" % ( - # hours, - # minutes - # ) - if self.days > 1: - ru_days = 'дней' - if 5 > self.days > 1: - ru_days = 'дня' - elif self.days == 1: - ru_days = 'день' - # text_date = '%d %s %s' % (self.days, ru_days, text_date) - text_date = '%d %s' % (self.days, ru_days) - else: - text_date = '' - return text_date - - -def require_ssl(view): - """ - Decorator that requires an SSL connection. If the current connection is not SSL, we redirect to the SSL version of - the page. - from: https://gist.github.com/ckinsey/9709984 - """ - - @wraps(view) - def wrapper(request, *args, **kwargs): - if not DEBUG and not request.is_secure(): - target_url = "https://" + request.META['HTTP_HOST'] + request.path_info - return HttpResponseRedirect(target_url) - return view(request, *args, **kwargs) - - return wrapper - - -class MultipleException(Exception): - def __init__(self, err_list): - if not isinstance(err_list, (list, tuple)): - raise TypeError - self.err_list = err_list - - -class LogicError(Exception): - pass - - -def singleton(class_): - instances = {} - - def getinstance(*args, **kwargs): - if class_ not in instances: - instances[class_] = class_(*args, **kwargs) - return instances[class_] - - return getinstance diff --git a/static/css/custom.css b/static/css/custom.css index 4e12389..3903f00 100644 --- a/static/css/custom.css +++ b/static/css/custom.css @@ -110,7 +110,7 @@ td.btn-group { border-left: 1px solid #ddd; border-right: 1px solid #ddd; border-bottom: 1px solid #ddd; - border-radius: 0px 0px 5px 5px; + border-radius: 0 0 5px 5px; padding: 10px; } @@ -209,8 +209,7 @@ a.port-img{ left: 0; } - .row-offcanvas-left - .sidebar-offcanvas { + .row-offcanvas-left .sidebar-offcanvas { left: -50%; /* 6 columns */ } @@ -299,3 +298,16 @@ pre { .m-icon_dollar{background-position: 0 -52px;} .m-icon_service{background-position: -26px -52px;} .m-icon_mrk{background-position: -52px -52px;} + + +@media (max-width: 991px) and (min-width: 768px){ + .container { + width: 874px; + } +} + + +/* Icon near to device info */ +.font-extra-large{ + font-size: 75px; +} From 00ff7475217b60d407f96592ad826147928eee0a Mon Sep 17 00:00:00 2001 From: bashmak Date: Thu, 31 May 2018 16:22:44 +0300 Subject: [PATCH 4/5] djing.lib refactoring --- .../abonapp/abon_confirm_delete.html | 22 +++----- abonapp/views.py | 30 +++++------ accounts_app/views.py | 13 ++--- devapp/locale/ru/LC_MESSAGES/django.po | 9 ++++ devapp/templates/devapp/dev.html | 2 +- .../devapp/device_confirm_delete.html | 14 ++++++ devapp/templates/devapp/devices.html | 2 +- .../templates/devapp/devices_null_group.html | 4 +- devapp/urls.py | 2 +- devapp/views.py | 35 +++++++------ dialing.py | 2 +- dialing_app/views.py | 3 +- djing/lib/__init__.py | 50 ------------------- djing/lib/decorators.py | 35 +++++++++++++ .../lib/messaging}/__init__.py | 0 djing/lib/messaging/sms/__init__.py | 7 +++ .../lib/messaging}/sms/base.py | 0 .../lib/messaging}/sms/consts.py | 0 .../lib/messaging}/sms/deliver.py | 10 ++-- .../lib/messaging}/sms/gsm0338.py | 0 {messaging => djing/lib/messaging}/sms/pdu.py | 0 .../lib/messaging}/sms/submit.py | 18 +++---- {messaging => djing/lib/messaging}/sms/udh.py | 0 {messaging => djing/lib/messaging}/sms/wap.py | 2 +- {messaging => djing/lib/messaging}/utils.py | 0 .../group_app/group_confirm_delete.html | 25 +++++----- messaging/sms/__init__.py | 7 --- tariff_app/locale/ru/LC_MESSAGES/django.po | 3 ++ .../tariff_app/modal_del_warning.html | 30 ----------- .../tariff_app/tariff_confirm_delete.html | 15 ++++++ tariff_app/urls.py | 2 +- tariff_app/views.py | 34 +++++++------ taskapp/views.py | 3 +- templates/base_delete_modal.html | 19 +++++++ 34 files changed, 209 insertions(+), 189 deletions(-) create mode 100644 devapp/templates/devapp/device_confirm_delete.html create mode 100644 djing/lib/decorators.py rename {messaging => djing/lib/messaging}/__init__.py (100%) create mode 100644 djing/lib/messaging/sms/__init__.py rename {messaging => djing/lib/messaging}/sms/base.py (100%) rename {messaging => djing/lib/messaging}/sms/consts.py (100%) rename {messaging => djing/lib/messaging}/sms/deliver.py (96%) rename {messaging => djing/lib/messaging}/sms/gsm0338.py (100%) rename {messaging => djing/lib/messaging}/sms/pdu.py (100%) rename {messaging => djing/lib/messaging}/sms/submit.py (94%) rename {messaging => djing/lib/messaging}/sms/udh.py (100%) rename {messaging => djing/lib/messaging}/sms/wap.py (95%) rename {messaging => djing/lib/messaging}/utils.py (100%) delete mode 100644 messaging/sms/__init__.py delete mode 100644 tariff_app/templates/tariff_app/modal_del_warning.html create mode 100644 tariff_app/templates/tariff_app/tariff_confirm_delete.html create mode 100644 templates/base_delete_modal.html diff --git a/abonapp/templates/abonapp/abon_confirm_delete.html b/abonapp/templates/abonapp/abon_confirm_delete.html index 664bc1b..5b21997 100644 --- a/abonapp/templates/abonapp/abon_confirm_delete.html +++ b/abonapp/templates/abonapp/abon_confirm_delete.html @@ -1,18 +1,10 @@ +{% extends 'base_delete_modal.html' %} {% load i18n %} -
    {% csrf_token %} - - - -
    +{% block modal_form_title %} + {% trans 'Remove subscriber' %} +{% endblock %} diff --git a/abonapp/views.py b/abonapp/views.py index 5a36b85..bd3215a 100644 --- a/abonapp/views.py +++ b/abonapp/views.py @@ -40,7 +40,7 @@ class BaseAbonListView(OrderingMixin, BaseListWithFiltering): http_method_names = ('get',) -@method_decorator((login_required, lib.only_admins), name='dispatch') +@method_decorator((login_required, lib.decorators.only_admins), name='dispatch') class PeoplesListView(BaseAbonListView): context_object_name = 'peoples' template_name = 'abonapp/peoples.html' @@ -85,7 +85,7 @@ class PeoplesListView(BaseAbonListView): return context -@method_decorator((login_required, lib.only_admins), name='dispatch') +@method_decorator((login_required, lib.decorators.only_admins), name='dispatch') class GroupListView(BaseAbonListView): context_object_name = 'groups' template_name = 'abonapp/group_list.html' @@ -98,7 +98,7 @@ class GroupListView(BaseAbonListView): return queryset -@method_decorator((login_required, lib.only_admins), name='dispatch') +@method_decorator((login_required, lib.decorators.only_admins), name='dispatch') @method_decorator(permission_required('abonapp.add_abon'), name='dispatch') class AbonCreateView(CreateView): group = None @@ -154,7 +154,7 @@ class AbonCreateView(CreateView): return super(AbonCreateView, self).form_invalid(form) -@method_decorator((login_required, lib.only_admins), name='dispatch') +@method_decorator((login_required, lib.decorators.only_admins), name='dispatch') @method_decorator(permission_required('abonapp.delete_abon'), name='dispatch') class DelAbonDeleteView(DeleteView): model = models.Abon @@ -214,7 +214,7 @@ def abonamount(request, gid, uname): }, request=request) -@method_decorator((login_required, lib.only_admins), name='dispatch') +@method_decorator((login_required, lib.decorators.only_admins), name='dispatch') @method_decorator(permission_required('group_app.can_view_group', (Group, 'pk', 'gid')), name='dispatch') class DebtsListView(BaseAbonListView): context_object_name = 'invoices' @@ -232,7 +232,7 @@ class DebtsListView(BaseAbonListView): return context -@method_decorator((login_required, lib.only_admins), name='dispatch') +@method_decorator((login_required, lib.decorators.only_admins), name='dispatch') @method_decorator(permission_required('group_app.can_view_group', (Group, 'pk', 'gid')), name='dispatch') class PayHistoryListView(BaseAbonListView): context_object_name = 'pay_history' @@ -252,7 +252,7 @@ class PayHistoryListView(BaseAbonListView): @login_required -@lib.only_admins +@lib.decorators.only_admins def abon_services(request, gid, uname): grp = get_object_or_404(Group, pk=gid) if not request.user.has_perm('group_app.can_view_group', grp): @@ -277,7 +277,7 @@ def abon_services(request, gid, uname): }) -@method_decorator((login_required, lib.only_admins), name='dispatch') +@method_decorator((login_required, lib.decorators.only_admins), name='dispatch') @method_decorator(permission_required('abonapp.change_abon'), name='post') class AbonHomeUpdateView(UpdateView): model = models.Abon @@ -406,7 +406,7 @@ def add_invoice(request, gid, uname): @login_required -@lib.only_admins +@lib.decorators.only_admins @permission_required('abonapp.can_buy_tariff') @transaction.atomic def pick_tariff(request, gid, uname): @@ -550,7 +550,7 @@ class PassportUpdateView(UpdateView): @login_required -@lib.only_admins +@lib.decorators.only_admins def chgroup_tariff(request, gid): grp = get_object_or_404(Group, pk=gid) if not request.user.has_perm('group_app.change_group', grp): @@ -766,7 +766,7 @@ def abon_ping(request): } -@method_decorator((login_required, lib.only_admins,), name='dispatch') +@method_decorator((login_required, lib.decorators.only_admins,), name='dispatch') class DialsListView(BaseAbonListView): context_object_name = 'logs' template_name = 'abonapp/dial_log.html' @@ -1028,7 +1028,7 @@ def reset_ip(request, gid, uname): @login_required -@lib.only_admins +@lib.decorators.only_admins def fin_report(request): q = models.AllTimePayLog.objects.by_days() res_format = request.GET.get('f') @@ -1088,7 +1088,7 @@ def del_periodic_pay(request, gid, uname, periodic_pay_id): return redirect('abonapp:abon_services', gid, uname) -@method_decorator((login_required, lib.only_admins,), name='dispatch') +@method_decorator((login_required, lib.decorators.only_admins,), name='dispatch') class EditSibscriberMarkers(UpdateView): http_method_names = ('get', 'post') template_name = 'abonapp/modal_user_markers.html' @@ -1119,7 +1119,7 @@ class EditSibscriberMarkers(UpdateView): # API's @login_required -@lib.only_admins +@lib.decorators.only_admins @json_view def abons(request): ablist = ({ @@ -1144,7 +1144,7 @@ def abons(request): @login_required -@lib.only_admins +@lib.decorators.only_admins @json_view def search_abon(request): word = request.GET.get('s') diff --git a/accounts_app/views.py b/accounts_app/views.py index 6cdd24e..9f3bbbd 100644 --- a/accounts_app/views.py +++ b/accounts_app/views.py @@ -15,6 +15,7 @@ from group_app.models import Group from .models import UserProfile from .forms import AvatarChangeForm from djing import lib +from djing.lib.decorators import only_admins from guardian.decorators import permission_required_or_403 as permission_required from guardian.shortcuts import get_objects_for_user, assign_perm, remove_perm @@ -61,7 +62,7 @@ class SignOut(RedirectView): @login_required -@lib.only_admins +@only_admins def profile_show(request, uid=0): uid = lib.safe_int(uid) @@ -86,7 +87,7 @@ def profile_show(request, uid=0): }) -@method_decorator((login_required, lib.only_admins), name='dispatch') +@method_decorator((login_required, only_admins), name='dispatch') class AvatarUpdateView(UpdateView): form_class = AvatarChangeForm template_name = 'accounts/settings/ch_info.html' @@ -99,7 +100,7 @@ class AvatarUpdateView(UpdateView): @login_required -@lib.only_admins +@only_admins def ch_info(request): if request.method == 'POST': user = request.user @@ -168,7 +169,7 @@ def create_profile(request): @login_required -@lib.only_admins +@only_admins def delete_profile(request, uid): prf = get_object_or_404(UserProfile, id=uid) if uid != request.user.id: @@ -179,7 +180,7 @@ def delete_profile(request, uid): return redirect('acc_app:accounts_list') -@method_decorator((login_required, lib.only_admins), name='dispatch') +@method_decorator((login_required, only_admins), name='dispatch') class AccountsListView(BaseAccListView): template_name = 'accounts/acc_list.html' context_object_name = 'users' @@ -287,7 +288,7 @@ def set_abon_groups_permission(request, uid): }) -@method_decorator((login_required, lib.only_admins), name='dispatch') +@method_decorator((login_required, only_admins), name='dispatch') class ManageResponsibilityGroups(ListView): http_method_names = ('get', 'post') template_name = 'accounts/manage_responsibility_groups.html' diff --git a/devapp/locale/ru/LC_MESSAGES/django.po b/devapp/locale/ru/LC_MESSAGES/django.po index b9f1eef..c9c3976 100644 --- a/devapp/locale/ru/LC_MESSAGES/django.po +++ b/devapp/locale/ru/LC_MESSAGES/django.po @@ -535,3 +535,12 @@ msgstr "Устройство %(device_name)s получило не опреде msgid "Ip address or parent device with ip address required for ONU device" msgstr "Необходим ip адрес. Или родительское устройство с назначенным ip адресом необходимо." + +msgid "Are you sure you want to delete device?" +msgstr "Вы уверены что хотите удалить устройство?" + +msgid "Remove device" +msgstr "Удалить устройство" + +msgid "Device successfully deleted" +msgstr "Устройство успешно удалено" diff --git a/devapp/templates/devapp/dev.html b/devapp/templates/devapp/dev.html index 31f2f9d..0b4e77a 100644 --- a/devapp/templates/devapp/dev.html +++ b/devapp/templates/devapp/dev.html @@ -61,7 +61,7 @@ {% trans 'Save' %} {% if perms.devapp.delete_device %} - + {% trans 'Delete' %} {% else %} diff --git a/devapp/templates/devapp/device_confirm_delete.html b/devapp/templates/devapp/device_confirm_delete.html new file mode 100644 index 0000000..961d650 --- /dev/null +++ b/devapp/templates/devapp/device_confirm_delete.html @@ -0,0 +1,14 @@ +{% extends 'base_delete_modal.html' %} +{% load i18n %} + +{% block modal_form_url %} + {% url 'devapp:del' object.group.pk object.pk %} +{% endblock %} + +{% block modal_form_title %} + {% trans 'Remove device' %} +{% endblock %} + +{% block modal_form_text %} +

    {% trans 'Are you sure you want to delete device?' %}

    +{% endblock %} diff --git a/devapp/templates/devapp/devices.html b/devapp/templates/devapp/devices.html index 4636d8b..248ae43 100644 --- a/devapp/templates/devapp/devices.html +++ b/devapp/templates/devapp/devices.html @@ -65,7 +65,7 @@ {{ dev.get_devtype_display }} {% if can_del_dev %} - + {% endif %} diff --git a/devapp/templates/devapp/devices_null_group.html b/devapp/templates/devapp/devices_null_group.html index cea6acb..3eeaaf0 100644 --- a/devapp/templates/devapp/devices_null_group.html +++ b/devapp/templates/devapp/devices_null_group.html @@ -46,12 +46,12 @@ {{ dev.get_devtype_display }} {% if can_del_dev %} - + {% endif %} {% if can_change_dev %} - + {% endif %} diff --git a/devapp/urls.py b/devapp/urls.py index 9731624..fdbf696 100644 --- a/devapp/urls.py +++ b/devapp/urls.py @@ -10,7 +10,7 @@ urlpatterns = [ url(r'^(?P\d+)$', views.DevicesListView.as_view(), name='devs'), url(r'^(?P\d+)/add$', views.dev, name='add'), url(r'^(\d+)/(?P\d+)$', views.devview, name='view'), - url(r'^(\d+)/(?P\d+)/del$', views.devdel, name='del'), + url(r'^(\d+)/(?P\d+)/del$', views.DeviceVeleteView.as_view(), name='del'), url(r'^(?P\d+)/(?P\d+)/add$', views.add_single_port, name='add_port'), url(r'^(?P\d+)/(?P\d+)/edit$', views.dev, name='edit'), url(r'^(\d+)/(?P\d+)/ports$', views.manage_ports, name='manage_ports'), diff --git a/devapp/views.py b/devapp/views.py index 5417e75..13caf28 100644 --- a/devapp/views.py +++ b/devapp/views.py @@ -11,10 +11,11 @@ from django.contrib import messages from django.utils.decorators import method_decorator from django.utils.translation import gettext_lazy as _, gettext from easysnmp import EasySNMPTimeoutError, EasySNMPError -from django.views.generic import DetailView +from django.views.generic import DetailView, DeleteView from devapp.base_intr import DeviceImplementationError -from djing.lib import res_success, res_error, only_admins, safe_int +from djing.lib.decorators import only_admins +from djing.lib import safe_int from abonapp.models import Abon from group_app.models import Group from accounts_app.models import UserProfile @@ -68,19 +69,23 @@ class DevicesWithoutGroupsListView(global_base_views.OrderingMixin, BaseDeviceLi queryset = Device.objects.filter(group=None).only('comment', 'devtype', 'pk', 'ip_address') -@login_required -@permission_required('devapp.delete_device') -def devdel(request, device_id): - try: - device_instance = Device.objects.get(pk=device_id) - back_url = resolve_url('devapp:devs', group_id=device_instance.group.pk if device_instance.group else 0) - device_instance.update_dhcp(remove=True) - device_instance.delete() - return res_success(request, back_url) - except Device.DoesNotExist: - return res_error(request, _('Delete failed')) - except DeviceDBException as e: - return res_error(request, e) +@method_decorator(login_required, name='dispatch') +@method_decorator(permission_required('devapp.delete_device'), name='dispatch') +class DeviceVeleteView(DeleteView): + model = Device + pk_url_kwarg = 'device_id' + + def get_success_url(self): + return resolve_url('devapp:devs', group_id=self.object.group.pk if self.object.group else 0) + + def delete(self, request, *args, **kwargs): + res = super().delete(request, *args, **kwargs) + try: + self.object.update_dhcp(remove=True) + except DeviceDBException as e: + messages.error(request, e) + messages.success(request, _('Device successfully deleted')) + return res @login_required diff --git a/dialing.py b/dialing.py index ab636f7..e8b5a4c 100755 --- a/dialing.py +++ b/dialing.py @@ -9,7 +9,7 @@ from urllib.error import HTTPError from hashlib import sha256 from pid.decorator import pidfile -from messaging.sms import SmsSubmit, SmsDeliver +from djing.lib.messaging.sms import SmsSubmit, SmsDeliver from asterisk import manager as ast_mngr diff --git a/dialing_app/views.py b/dialing_app/views.py index 0108d26..9c221bd 100644 --- a/dialing_app/views.py +++ b/dialing_app/views.py @@ -16,7 +16,8 @@ from jsonview.decorators import json_view from abonapp.models import Abon from djing.global_base_views import SecureApiView from djing import JSONType -from djing.lib import only_admins, safe_int +from djing.lib import safe_int +from djing.lib.decorators import only_admins from .models import AsteriskCDR, SMSModel, SMSOut from .forms import SMSOutForm diff --git a/djing/lib/__init__.py b/djing/lib/__init__.py index 948f5d5..fc0e76d 100644 --- a/djing/lib/__init__.py +++ b/djing/lib/__init__.py @@ -2,14 +2,7 @@ import socket import struct from datetime import timedelta from collections import Iterator -from functools import wraps -from json import dumps -from django.http import HttpResponse, Http404, HttpResponseRedirect -from django.shortcuts import redirect from django.db import models -from django.conf import settings - -DEBUG = getattr(settings, 'DEBUG', False) def ip2int(addr): @@ -40,20 +33,6 @@ def safe_int(i): return 0 -def res_success(request, redirect_to='/'): - if request.is_ajax(): - return HttpResponse(dumps({'errnum': 0})) - else: - return redirect(redirect_to) - - -def res_error(request, text): - if request.is_ajax(): - return HttpResponse(dumps({'errnum': 1, 'errtext': text})) - else: - raise Http404(text) - - class MyGenericIPAddressField(models.GenericIPAddressField): description = "Int32 notation ip address" @@ -104,18 +83,6 @@ class MyChoicesAdapter(Iterator): return res -# Allow to view only admins -def only_admins(fn): - @wraps(fn) - def wrapped(request, *args, **kwargs): - if request.user.is_admin: - return fn(request, *args, **kwargs) - else: - return redirect('client_side:home') - - return wrapped - - # Russian localized timedelta class RuTimedelta(timedelta): def __new__(cls, tm): @@ -147,23 +114,6 @@ class RuTimedelta(timedelta): return text_date -def require_ssl(view): - """ - Decorator that requires an SSL connection. If the current connection is not SSL, we redirect to the SSL version of - the page. - from: https://gist.github.com/ckinsey/9709984 - """ - - @wraps(view) - def wrapper(request, *args, **kwargs): - if not DEBUG and not request.is_secure(): - target_url = "https://" + request.META['HTTP_HOST'] + request.path_info - return HttpResponseRedirect(target_url) - return view(request, *args, **kwargs) - - return wrapper - - class MultipleException(Exception): def __init__(self, err_list): if not isinstance(err_list, (list, tuple)): diff --git a/djing/lib/decorators.py b/djing/lib/decorators.py new file mode 100644 index 0000000..9be54a5 --- /dev/null +++ b/djing/lib/decorators.py @@ -0,0 +1,35 @@ +from functools import wraps +from django.conf import settings +from django.http import HttpResponseRedirect +from django.shortcuts import redirect + + +DEBUG = getattr(settings, 'DEBUG', False) + + +def require_ssl(view): + """ + Decorator that requires an SSL connection. If the current connection is not SSL, we redirect to the SSL version of + the page. + from: https://gist.github.com/ckinsey/9709984 + """ + + @wraps(view) + def wrapper(request, *args, **kwargs): + if not DEBUG and not request.is_secure(): + target_url = "https://" + request.META['HTTP_HOST'] + request.path_info + return HttpResponseRedirect(target_url) + return view(request, *args, **kwargs) + + return wrapper + + +# Allow to view only admins +def only_admins(fn): + @wraps(fn) + def wrapped(request, *args, **kwargs): + if request.user.is_admin: + return fn(request, *args, **kwargs) + else: + return redirect('client_side:home') + return wrapped diff --git a/messaging/__init__.py b/djing/lib/messaging/__init__.py similarity index 100% rename from messaging/__init__.py rename to djing/lib/messaging/__init__.py diff --git a/djing/lib/messaging/sms/__init__.py b/djing/lib/messaging/sms/__init__.py new file mode 100644 index 0000000..0e78d70 --- /dev/null +++ b/djing/lib/messaging/sms/__init__.py @@ -0,0 +1,7 @@ +# See LICENSE + +from djing.lib.messaging.sms.deliver import SmsDeliver +from djing.lib.messaging.sms.submit import SmsSubmit +from djing.lib.messaging.sms.gsm0338 import is_gsm_text + +__all__ = ("SmsSubmit", "SmsDeliver", "is_gsm_text") diff --git a/messaging/sms/base.py b/djing/lib/messaging/sms/base.py similarity index 100% rename from messaging/sms/base.py rename to djing/lib/messaging/sms/base.py diff --git a/messaging/sms/consts.py b/djing/lib/messaging/sms/consts.py similarity index 100% rename from messaging/sms/consts.py rename to djing/lib/messaging/sms/consts.py diff --git a/messaging/sms/deliver.py b/djing/lib/messaging/sms/deliver.py similarity index 96% rename from messaging/sms/deliver.py rename to djing/lib/messaging/sms/deliver.py index 5036e46..1efa925 100644 --- a/messaging/sms/deliver.py +++ b/djing/lib/messaging/sms/deliver.py @@ -3,11 +3,11 @@ from datetime import datetime, timedelta -from messaging.utils import (swap, swap_number, encode_bytes, debug, - unpack_msg, unpack_msg2, to_array) -from messaging.sms import consts -from messaging.sms.base import SmsBase -from messaging.sms.udh import UserDataHeader +from djing.lib.messaging.utils import (swap, swap_number, encode_bytes, debug, + unpack_msg, unpack_msg2, to_array) +from djing.lib.messaging.sms import consts +from djing.lib.messaging.sms.submit import SmsBase +from djing.lib.messaging.sms.udh import UserDataHeader class SmsDeliver(SmsBase): diff --git a/messaging/sms/gsm0338.py b/djing/lib/messaging/sms/gsm0338.py similarity index 100% rename from messaging/sms/gsm0338.py rename to djing/lib/messaging/sms/gsm0338.py diff --git a/messaging/sms/pdu.py b/djing/lib/messaging/sms/pdu.py similarity index 100% rename from messaging/sms/pdu.py rename to djing/lib/messaging/sms/pdu.py diff --git a/messaging/sms/submit.py b/djing/lib/messaging/sms/submit.py similarity index 94% rename from messaging/sms/submit.py rename to djing/lib/messaging/sms/submit.py index f0e9941..1bdb3cd 100644 --- a/messaging/sms/submit.py +++ b/djing/lib/messaging/sms/submit.py @@ -4,15 +4,15 @@ from datetime import datetime, timedelta import re -from messaging.sms import consts -from messaging.utils import (debug, encode_str, clean_number, - pack_8bits_to_ucs2, pack_8bits_to_7bits, - pack_8bits_to_8bit, - timedelta_to_relative_validity, - datetime_to_absolute_validity) -from messaging.sms.base import SmsBase -from messaging.sms.gsm0338 import is_gsm_text -from messaging.sms.pdu import Pdu +from djing.lib.messaging.sms import consts +from djing.lib.messaging.utils import (debug, encode_str, clean_number, + pack_8bits_to_ucs2, pack_8bits_to_7bits, + pack_8bits_to_8bit, + timedelta_to_relative_validity, + datetime_to_absolute_validity) +from djing.lib.messaging.sms.base import SmsBase +from djing.lib.messaging.sms.gsm0338 import is_gsm_text +from djing.lib.messaging.sms.pdu import Pdu VALID_NUMBER = re.compile("^\+?\d{3,20}$") diff --git a/messaging/sms/udh.py b/djing/lib/messaging/sms/udh.py similarity index 100% rename from messaging/sms/udh.py rename to djing/lib/messaging/sms/udh.py diff --git a/messaging/sms/wap.py b/djing/lib/messaging/sms/wap.py similarity index 95% rename from messaging/sms/wap.py rename to djing/lib/messaging/sms/wap.py index 46611ab..859c952 100644 --- a/messaging/sms/wap.py +++ b/djing/lib/messaging/sms/wap.py @@ -2,7 +2,7 @@ from array import array -from messaging.mms.mms_pdu import MMSDecoder +from djing.lib.messaging import MMSDecoder def is_a_wap_push_notification(s): diff --git a/messaging/utils.py b/djing/lib/messaging/utils.py similarity index 100% rename from messaging/utils.py rename to djing/lib/messaging/utils.py diff --git a/group_app/templates/group_app/group_confirm_delete.html b/group_app/templates/group_app/group_confirm_delete.html index cfa1590..27f9fc9 100644 --- a/group_app/templates/group_app/group_confirm_delete.html +++ b/group_app/templates/group_app/group_confirm_delete.html @@ -1,13 +1,14 @@ +{% extends 'base_delete_modal.html' %} {% load i18n %} -
    {% csrf_token %} - - -
    + +{% block modal_form_url %} + {% url 'group_app:del' object.pk %} +{% endblock %} + +{% block modal_form_title %} + {% trans 'Remove group' %} +{% endblock %} + +{% block modal_form_text %} +

    {% blocktrans %}Are you sure you want to delete group {{ object }}?{% endblocktrans %}

    +{% endblock %} diff --git a/messaging/sms/__init__.py b/messaging/sms/__init__.py deleted file mode 100644 index f00ad10..0000000 --- a/messaging/sms/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# See LICENSE - -from messaging.sms.submit import SmsSubmit -from messaging.sms.deliver import SmsDeliver -from messaging.sms.gsm0338 import is_gsm_text - -__all__ = ["SmsSubmit", "SmsDeliver", "is_gsm_text"] diff --git a/tariff_app/locale/ru/LC_MESSAGES/django.po b/tariff_app/locale/ru/LC_MESSAGES/django.po index 08b181f..79aa109 100644 --- a/tariff_app/locale/ru/LC_MESSAGES/django.po +++ b/tariff_app/locale/ru/LC_MESSAGES/django.po @@ -209,3 +209,6 @@ msgstr "Новый периодический платёж создан" #: tariff_app/views.py:102 msgid "Periodic pay has been changed" msgstr "Периодический платёж изменён" + +msgid "Are you sure you want to delete tariff?" +msgstr "Вы уверены что хотите удалить тариф?" diff --git a/tariff_app/templates/tariff_app/modal_del_warning.html b/tariff_app/templates/tariff_app/modal_del_warning.html deleted file mode 100644 index fbe951d..0000000 --- a/tariff_app/templates/tariff_app/modal_del_warning.html +++ /dev/null @@ -1,30 +0,0 @@ -{% load i18n %} - -
    {% csrf_token %} - - - {% include 'message_block.html' %} - - - -
    diff --git a/tariff_app/templates/tariff_app/tariff_confirm_delete.html b/tariff_app/templates/tariff_app/tariff_confirm_delete.html new file mode 100644 index 0000000..60070e6 --- /dev/null +++ b/tariff_app/templates/tariff_app/tariff_confirm_delete.html @@ -0,0 +1,15 @@ +{% extends 'base_delete_modal.html' %} +{% load i18n %} + +{% block modal_form_url %} + {% url 'tarifs:del' tid %} +{% endblock %} + +{% block modal_form_title %} + {% trans 'Delete service' %} +{% endblock %} + +{% block modal_form_text %} + +

    {% blocktrans %}after delete the tariff, subscribers who use that tariff will be disconnected from it.{% endblocktrans %}

    +{% endblock %} diff --git a/tariff_app/urls.py b/tariff_app/urls.py index 1548b9e..727996a 100644 --- a/tariff_app/urls.py +++ b/tariff_app/urls.py @@ -8,7 +8,7 @@ urlpatterns = [ url(r'^$', views.TariffsListView.as_view(), name='home'), url(r'^(?P\d+)$', views.edit_tarif, name='edit'), url(r'^add$', views.edit_tarif, name='add'), - url(r'^del(?P\d+)$', views.del_tarif, name='del'), + url(r'^del(?P\d+)$', views.TariffDeleteView.as_view(), name='del'), url(r'^periodic_pays$', views.PeriodicPaysListView.as_view(), name='periodic_pays'), url(r'^periodic_pays/add$', views.periodic_pay, name='periodic_pay_add'), diff --git a/tariff_app/views.py b/tariff_app/views.py index 1f0d7d9..13e5aaa 100644 --- a/tariff_app/views.py +++ b/tariff_app/views.py @@ -1,12 +1,12 @@ -# -*- coding: utf-8 -*- from django.contrib.auth.decorators import login_required from django.contrib.gis.shortcuts import render_to_text +from django.urls import reverse_lazy from django.utils.decorators import method_decorator from django.utils.translation import ugettext as _ from django.shortcuts import render, get_object_or_404, redirect from django.contrib import messages from django.core.exceptions import PermissionDenied -from django.views.generic import ListView +from django.views.generic import ListView, DeleteView from django.conf import settings from guardian.decorators import permission_required_or_403 as permission_required @@ -21,7 +21,7 @@ class BaseServiceListView(ListView): paginate_by = getattr(settings, 'PAGINATION_ITEMS_PER_PAGE', 10) -@method_decorator((login_required, lib.only_admins), name='dispatch') +@method_decorator((login_required, lib.decorators.only_admins), name='dispatch') class TariffsListView(BaseServiceListView, OrderingMixin): """ Show Services(Tariffs) list @@ -61,21 +61,25 @@ def edit_tarif(request, tarif_id=0): }) -@login_required -@permission_required('tariff_app.delete_tariff') -def del_tarif(request, tid): - if request.method == 'POST': - if request.POST.get('confirm') == 'yes': - get_object_or_404(Tariff, id=tid).delete() - messages.success(request, _('Service has been deleted')) - else: - messages.error(request, _('Not have a confirmations of delete')) - return lib.res_success(request, 'tarifs:home') - return render_to_text('tariff_app/modal_del_warning.html', {'tid': tid}, request=request) +@method_decorator(login_required, name='dispatch') +@method_decorator(permission_required('tariff_app.delete_tariff'), name='dispatch') +class TariffDeleteView(DeleteView): + model = Tariff + pk_url_kwarg = 'tid' + success_url = reverse_lazy('tarifs:home') + + def delete(self, request, *args, **kwargs): + res = super().delete(request, *args, **kwargs) + messages.success(request, _('Service has been deleted')) + return res + + def get_context_data(self, **kwargs): + kwargs['tid'] = self.kwargs.get('tid') + return super().get_context_data(**kwargs) @method_decorator(login_required, name='dispatch') -@method_decorator(permission_required('tariff_app.can_view_periodic_pay'), name='dispatch') +@method_decorator(permission_required('tariff_app.delete_tariff'), name='dispatch') class PeriodicPaysListView(BaseServiceListView): context_object_name = 'pays' model = PeriodicPay diff --git a/taskapp/views.py b/taskapp/views.py index b0624a8..2e53022 100644 --- a/taskapp/views.py +++ b/taskapp/views.py @@ -18,7 +18,8 @@ from jsonview.decorators import json_view from chatbot.models import MessageQueue from abonapp.models import Abon from djing import httpresponse_to_referrer -from djing.lib import only_admins, safe_int, MultipleException, RuTimedelta +from djing.lib import safe_int, MultipleException, RuTimedelta +from djing.lib.decorators import only_admins from .handle import TaskException from .models import Task, ExtraComment from .forms import TaskFrm, ExtraCommentForm diff --git a/templates/base_delete_modal.html b/templates/base_delete_modal.html new file mode 100644 index 0000000..39c9cdd --- /dev/null +++ b/templates/base_delete_modal.html @@ -0,0 +1,19 @@ +{% load i18n %} +
    {% csrf_token %} + + +
    From d5eeeec38f592095707b3ec69812b83bdb39a79b Mon Sep 17 00:00:00 2001 From: bashmak Date: Sat, 2 Jun 2018 18:14:32 +0300 Subject: [PATCH 5/5] Make telnet script for ZTE-C320. And maked snmp field validate in dynamic device types --- agent/mod_mikrotik.py | 11 +- devapp/base_intr.py | 15 +- devapp/dev_types.py | 24 ++ devapp/forms.py | 18 +- devapp/locale/ru/LC_MESSAGES/django.po | 433 ++++++++++++++----------- devapp/models.py | 9 +- devapp/templates/devapp/devices.html | 2 - djing/lib/__init__.py | 15 +- djing/lib/tln/__init__.py | 4 + djing/lib/tln/tln.py | 266 +++++++++++++++ 10 files changed, 588 insertions(+), 209 deletions(-) create mode 100644 djing/lib/tln/__init__.py create mode 100755 djing/lib/tln/tln.py diff --git a/agent/mod_mikrotik.py b/agent/mod_mikrotik.py index 3f246ce..cdb3634 100644 --- a/agent/mod_mikrotik.py +++ b/agent/mod_mikrotik.py @@ -6,7 +6,7 @@ from abc import ABCMeta from hashlib import md5 from typing import Iterable, Optional, Tuple from .core import BaseTransmitter, NasFailedResult, NasNetworkError -from djing.lib import singleton +from djing.lib import Singleton from .structs import TariffStruct, AbonStruct, IpStruct, VectorAbon, VectorTariff from . import settings as local_settings from django.conf import settings @@ -18,8 +18,7 @@ LIST_USERS_ALLOWED = 'DjingUsersAllowed' LIST_USERS_BLOCKED = 'DjingUsersBlocked' -@singleton -class ApiRos: +class ApiRos(metaclass=Singleton): """Routeros api""" sk = None is_login = False @@ -223,8 +222,8 @@ class TransmitterManager(BaseTransmitter, metaclass=ABCMeta): speeds = info['=max-limit'].split('/') t = TariffStruct( - speedIn=parse_speed(speeds[1]), - speedOut=parse_speed(speeds[0]) + speed_in=parse_speed(speeds[1]), + speed_out=parse_speed(speeds[0]) ) try: a = AbonStruct( @@ -480,7 +479,7 @@ class MikrotikTransmitter(QueueManager, IpAddressListManager): interface = r[0]['=interface'] r = self._exec_cmd(( '/ping', '=address=%s' % host, '=arp-ping=yes', '=interval=100ms', '=count=%d' % count, - '=interface=%s' % interface + '=interface=%s' % interface )) received, sent = int(r[-2:][0]['=received']), int(r[-2:][0]['=sent']) return received, sent diff --git a/devapp/base_intr.py b/devapp/base_intr.py index 5725e66..a1dee37 100644 --- a/devapp/base_intr.py +++ b/devapp/base_intr.py @@ -1,10 +1,10 @@ from abc import ABCMeta, abstractmethod from datetime import timedelta -from django.utils.translation import gettext from typing import Union, Iterable, AnyStr, Generator, Optional - from easysnmp import Session +from django.utils.translation import gettext + ListOrError = Union[ Iterable, Union[Exception, Iterable] @@ -15,7 +15,7 @@ class DeviceImplementationError(Exception): pass -class DevBase(object, metaclass=ABCMeta): +class DevBase(object): def __init__(self, dev_instance=None): self.db_instance = dev_instance @@ -53,6 +53,15 @@ class DevBase(object, metaclass=ABCMeta): def is_use_device_port() -> bool: """True if used device port while opt82 authorization""" + @abstractmethod + def validate_extra_snmp_info(self, v: str) -> None: + """ + Validate extra snmp field for each device. + If validation failed then raise en exception from djing.lib.tln.ValidationError + with description of error. + :param v: String value for validate + """ + class BasePort(object, metaclass=ABCMeta): def __init__(self, num, name, status, mac, speed): diff --git a/devapp/dev_types.py b/devapp/dev_types.py index c6c46c7..a785b79 100644 --- a/devapp/dev_types.py +++ b/devapp/dev_types.py @@ -4,6 +4,7 @@ from easysnmp import EasySNMPTimeoutError 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 .base_intr import DevBase, SNMPBaseWorker, BasePort, DeviceImplementationError, ListOrError @@ -77,6 +78,10 @@ class DLinkDevice(DevBase, SNMPBaseWorker): def is_use_device_port(): return True + def validate_extra_snmp_info(self, v: str) -> None: + # Dlink has no require snmp info + pass + class ONUdev(BasePort): def __init__(self, num, name, status, mac, speed, signal, snmp_worker): @@ -150,6 +155,10 @@ class OLTDevice(DevBase, SNMPBaseWorker): def is_use_device_port(): return False + def validate_extra_snmp_info(self, v: str) -> None: + # Olt has no require snmp info + pass + class OnuDevice(DevBase, SNMPBaseWorker): def __init__(self, dev_instance): @@ -216,6 +225,13 @@ class OnuDevice(DevBase, SNMPBaseWorker): except EasySNMPTimeoutError as e: return {'err': "%s: %s" % (_('ONU not connected'), e)} + def validate_extra_snmp_info(self, v: str) -> None: + # DBCOM Onu have en integer snmp port + try: + int(v) + except ValueError: + raise TlnValidationError(_('Onu snmp field must be en integer')) + class EltexPort(BasePort): def __init__(self, snmp_worker, *args, **kwargs): @@ -360,3 +376,11 @@ class ZteOnuDevice(OnuDevice): def get_template_name(self): return 'onu_for_zte.html' + + def validate_extra_snmp_info(self, v: str) -> None: + # for example 268501760.5 + try: + fiber_num, onu_port = v.split('.') + int(fiber_num), int(onu_port) + except ValueError: + raise TlnValidationError(_('Zte onu snmp field must be two dot separated integers')) diff --git a/devapp/forms.py b/devapp/forms.py index c3145c4..dd67e5b 100644 --- a/devapp/forms.py +++ b/devapp/forms.py @@ -1,7 +1,9 @@ from django import forms +from django.core.exceptions import ValidationError from django.utils.translation import gettext_lazy as _ from django.db import IntegrityError +from djing.lib.tln.tln import ValidationError as TlnValidationError from . import models from djing import MAC_ADDR_REGEX, IP_ADDR_REGEX @@ -15,9 +17,23 @@ class DeviceForm(forms.ModelForm): 'unique': _('Device with that mac is already exist') }) + def clean_snmp_extra(self): + snmp_extra = self.cleaned_data.get('snmp_extra') + if snmp_extra is None: + return + device = self.instance + manager = device.get_manager_object() + try: + manager.validate_extra_snmp_info(snmp_extra) + except TlnValidationError as e: + raise ValidationError( + e, code='invalid' + ) + return snmp_extra + class Meta: model = models.Device - exclude = ['map_dot', 'status'] + exclude = ('map_dot', 'status') widgets = { 'ip_address': forms.TextInput(attrs={ 'pattern': IP_ADDR_REGEX, diff --git a/devapp/locale/ru/LC_MESSAGES/django.po b/devapp/locale/ru/LC_MESSAGES/django.po index c9c3976..9b685a4 100644 --- a/devapp/locale/ru/LC_MESSAGES/django.po +++ b/devapp/locale/ru/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-04-24 23:27+0300\n" +"POT-Creation-Date: 2018-06-02 16:39+0300\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: Dmitry Novikov nerosketch@gmail.com\n" "Language: ru\n" @@ -18,377 +18,456 @@ msgstr "" "%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n" "%100>=11 && n%100<=14)? 2 : 3);\n" -#: devapp/base_intr.py:82 +#: base_intr.py:92 msgid "Ip address is required" msgstr "Ip адрес необходим" -#: devapp/dev_types.py:35 +#: dev_types.py:37 msgid "DLink switch" msgstr "Свич D'Link" -#: devapp/dev_types.py:54 +#: dev_types.py:56 msgid "does not fetch the mac" msgstr "не нашёл мак" -#: devapp/dev_types.py:106 +#: dev_types.py:112 msgid "PON OLT" msgstr "" -#: devapp/dev_types.py:131 devapp/views.py:268 devapp/views.py:413 +#: dev_types.py:136 views.py:275 views.py:433 msgid "wait for a reply from the SNMP Timeout" msgstr "Время ожидания ответа от SNMP истекло" -#: devapp/dev_types.py:171 +#: dev_types.py:176 +msgid "Ip address or parent device with ip address required for ONU device" +msgstr "" +"Необходим ip адрес. Или родительское устройство с назначенным ip адресом " +"необходимо." + +#: dev_types.py:182 msgid "PON ONU" msgstr "" -#: devapp/dev_types.py:216 +#: dev_types.py:227 msgid "ONU not connected" msgstr "ONU не в сети" -#: devapp/dev_types.py:242 +#: dev_types.py:234 +msgid "Onu snmp field must be en integer" +msgstr "Поле для snmp информации об ONU должно быть числом" + +#: dev_types.py:260 msgid "Eltex switch" msgstr "Элтекс свич" -#: devapp/forms.py:14 +#: dev_types.py:305 +msgid "OLT ZTE C320" +msgstr "" + +#: dev_types.py:354 +msgid "ZTE PON ONU" +msgstr "" + +#: dev_types.py:387 +msgid "Zte onu snmp field must be two dot separated integers" +msgstr "" +"Поле snmp информации для ZTE ONU должно быть двумя целыми числами, " +"разделенными точкой" + +#: forms.py:16 msgid "Mac address is required for fill" msgstr "MAC-адрес необходим для заполнения" -#: devapp/forms.py:15 +#: forms.py:17 msgid "Device with that mac is already exist" msgstr "Устройство с этим мак-адресом уже есть" -#: devapp/forms.py:48 +#: forms.py:63 msgid "Port number on device must be unique" msgstr "Номер порта на устройстве должен быть уникальным" -#: devapp/models.py:21 devapp/templates/devapp/custom_dev_page/onu.html:18 -#: devapp/templates/devapp/devices.html:22 -#: devapp/templates/devapp/devices_null_group.html:20 +#: models.py:25 templates/devapp/custom_dev_page/onu.html:18 +#: templates/devapp/custom_dev_page/onu_for_zte.html:18 +#: templates/devapp/devices.html:22 templates/devapp/devices_null_group.html:20 msgid "Ip address" msgstr "Ip адрес" -#: devapp/models.py:22 devapp/templates/devapp/devices.html:32 +#: models.py:26 templates/devapp/devices.html:32 msgid "Mac address" msgstr "Мак адрес" -#: devapp/models.py:23 devapp/templates/devapp/devices.html:28 -#: devapp/templates/devapp/devices_null_group.html:26 +#: models.py:27 templates/devapp/devices.html:28 +#: templates/devapp/devices_null_group.html:26 msgid "Comment" msgstr "Комментарий" -#: devapp/models.py:30 devapp/templates/devapp/devices.html:36 -#: devapp/templates/devapp/devices_null_group.html:32 +#: models.py:36 templates/devapp/devices.html:36 +#: templates/devapp/devices_null_group.html:32 msgid "Device type" msgstr "Тип устройства" -#: devapp/models.py:32 +#: models.py:38 msgid "SNMP password" msgstr "Пароль SNMP" -#: devapp/models.py:33 +#: models.py:39 msgid "Device group" msgstr "Группа устройства" -#: devapp/models.py:34 devapp/templates/devapp/add_dev.html:46 -#: devapp/templates/devapp/custom_dev_page/onu.html:34 -#: devapp/templates/devapp/custom_dev_page/ports.html:59 -#: devapp/templates/devapp/dev.html:33 -#: devapp/templates/devapp/fix_dev_group.html:42 +#: models.py:40 templates/devapp/add_dev.html:46 +#: templates/devapp/custom_dev_page/onu.html:34 +#: templates/devapp/custom_dev_page/onu_for_zte.html:34 +#: templates/devapp/custom_dev_page/ports.html:59 templates/devapp/dev.html:33 +#: templates/devapp/fix_dev_group.html:42 msgid "Parent device" msgstr "Родительское устройство" -#: devapp/models.py:37 -msgid "SNMP Number" -msgstr "SNMP Ном." +#: models.py:43 +msgid "SNMP extra info" +msgstr "Доп. инфо для snmp" -#: devapp/models.py:40 +#: models.py:46 msgid "Undefined" msgstr "Не определено" -#: devapp/models.py:41 +#: models.py:47 msgid "Up" msgstr "В сети" -#: devapp/models.py:42 +#: models.py:48 msgid "Unreachable" msgstr "Не доступно" -#: devapp/models.py:43 +#: models.py:49 msgid "Down" msgstr "Не в сети" -#: devapp/models.py:45 +#: models.py:51 msgid "Status" msgstr "Состояние" -#: devapp/models.py:47 +#: models.py:53 msgid "Send notify when monitoring state changed" msgstr "Отправлять уведомления при событиях мониторинга" -#: devapp/models.py:52 +#: models.py:58 msgid "Can view device" msgstr "Может видеть устройство" -#: devapp/models.py:54 devapp/models.py:126 +#: models.py:60 models.py:108 msgid "Device" msgstr "Устройство" -#: devapp/models.py:55 devapp/templates/devapp/devices.html:14 -#: devapp/templates/devapp/devices_null_group.html:8 +#: models.py:61 templates/devapp/devices.html:14 +#: templates/devapp/devices_null_group.html:8 msgid "Devices" msgstr "Устройства" -#: devapp/models.py:127 devapp/templates/devapp/manage_ports/list.html:11 +#: models.py:96 views.py:175 views.py:237 +msgid "Device does not have a group, please fix that" +msgstr "У устройства нет группы, пожалуйста, исправьте это" + +#: models.py:109 templates/devapp/manage_ports/list.html:11 msgid "Number" msgstr "Номер" -#: devapp/models.py:128 devapp/templates/devapp/custom_dev_page/onu.html:20 -#: devapp/templates/devapp/custom_dev_page/ports.html:88 -#: devapp/templates/devapp/manage_ports/add_ports.html:33 -#: devapp/templates/devapp/manage_ports/list.html:12 +#: models.py:110 templates/devapp/custom_dev_page/onu.html:20 +#: templates/devapp/custom_dev_page/onu_for_zte.html:20 +#: templates/devapp/custom_dev_page/ports.html:88 +#: templates/devapp/manage_ports/add_ports.html:33 +#: templates/devapp/manage_ports/list.html:12 msgid "Description" msgstr "Описание" -#: devapp/models.py:137 +#: models.py:119 msgid "Can toggle ports" msgstr "Может переключать порты" -#: devapp/models.py:139 devapp/templates/devapp/custom_dev_page/ports.html:110 -#: devapp/templates/devapp/manage_ports/fix_abon_device.html:24 +#: models.py:121 templates/devapp/custom_dev_page/ports.html:110 +#: templates/devapp/manage_ports/fix_abon_device.html:24 msgid "Port" msgstr "Порт" -#: devapp/models.py:140 +#: models.py:122 msgid "Ports" msgstr "Порты" -#: devapp/templates/devapp/add_dev.html:8 -#: devapp/templates/devapp/devices.html:8 -#: devapp/templates/devapp/devices_null_group.html:7 -#: devapp/templates/devapp/fix_dev_group.html:9 -#: devapp/templates/devapp/group_list.html:7 -#: devapp/templates/devapp/manage_ports/add_ports.html:7 -#: devapp/templates/devapp/manage_ports/fix_abon_device.html:7 +#: templates/devapp/add_dev.html:8 +#: templates/devapp/custom_dev_page/olt_ztec320_ports.html:7 +#: templates/devapp/devices.html:8 templates/devapp/devices_null_group.html:7 +#: templates/devapp/fix_dev_group.html:9 templates/devapp/group_list.html:7 +#: templates/devapp/manage_ports/add_ports.html:7 +#: templates/devapp/manage_ports/fix_abon_device.html:7 msgid "Groups" msgstr "Группы" -#: devapp/templates/devapp/add_dev.html:10 +#: templates/devapp/add_dev.html:10 msgid "Add new device" msgstr "Добавить устройство" -#: devapp/templates/devapp/add_dev.html:16 -#: devapp/templates/devapp/custom_dev_page/ports.html:68 -#: devapp/templates/devapp/devices.html:63 -#: devapp/templates/devapp/manage_ports/add_ports.html:16 +#: templates/devapp/add_dev.html:16 +#: templates/devapp/custom_dev_page/olt_ztec320_ports.html:12 +#: templates/devapp/custom_dev_page/ports.html:68 +#: templates/devapp/devices.html:63 +#: templates/devapp/manage_ports/add_ports.html:16 msgid "Not assigned" msgstr "<Не назначено>" -#: devapp/templates/devapp/add_dev.html:21 devapp/templates/devapp/dev.html:8 +#: templates/devapp/add_dev.html:21 templates/devapp/dev.html:8 msgid "Device info" msgstr "Инфа о железке" -#: devapp/templates/devapp/add_dev.html:60 devapp/templates/devapp/dev.html:43 -#: devapp/templates/devapp/dev.html:47 -#: devapp/templates/devapp/fix_dev_group.html:51 -#: devapp/templates/devapp/fix_dev_group.html:55 +#: templates/devapp/add_dev.html:60 templates/devapp/dev.html:43 +#: templates/devapp/dev.html:47 templates/devapp/fix_dev_group.html:51 +#: templates/devapp/fix_dev_group.html:55 msgid "Find the device" msgstr "Найти устройство" -#: devapp/templates/devapp/add_dev.html:72 devapp/templates/devapp/dev.html:61 -#: devapp/templates/devapp/fix_dev_group.html:64 -#: devapp/templates/devapp/manage_ports/add_ports.html:75 -#: devapp/templates/devapp/manage_ports/add_ports.html:79 -#: devapp/templates/devapp/manage_ports/modal_add_edit_port.html:21 +#: templates/devapp/add_dev.html:72 templates/devapp/dev.html:61 +#: templates/devapp/fix_dev_group.html:64 +#: templates/devapp/manage_ports/add_ports.html:75 +#: templates/devapp/manage_ports/add_ports.html:79 +#: templates/devapp/manage_ports/modal_add_edit_port.html:21 msgid "Save" msgstr "Сохранить" -#: devapp/templates/devapp/add_dev.html:75 devapp/templates/devapp/dev.html:73 -#: devapp/templates/devapp/fix_dev_group.html:67 +#: templates/devapp/add_dev.html:75 templates/devapp/dev.html:73 +#: templates/devapp/fix_dev_group.html:67 msgid "Reset" msgstr "Сбросить" -#: devapp/templates/devapp/custom_dev_page/olt.html:10 -#: devapp/templates/devapp/custom_dev_page/onu.html:12 -#: devapp/templates/devapp/custom_dev_page/ports.html:12 +#: templates/devapp/custom_dev_page/olt.html:10 +#: templates/devapp/custom_dev_page/olt_ztec320.html:11 +#: templates/devapp/custom_dev_page/onu.html:12 +#: templates/devapp/custom_dev_page/onu_for_zte.html:12 +#: templates/devapp/custom_dev_page/ports.html:12 msgid "Uptime" msgstr "Без перезагрузки" -#: devapp/templates/devapp/custom_dev_page/olt.html:17 +#: templates/devapp/custom_dev_page/olt.html:17 msgid "SNMP Num" msgstr "SNMP Ном." -#: devapp/templates/devapp/custom_dev_page/olt.html:18 +#: templates/devapp/custom_dev_page/olt.html:18 msgid "Name" msgstr "Имя" -#: devapp/templates/devapp/custom_dev_page/olt.html:19 -#: devapp/templates/devapp/custom_dev_page/onu.html:19 +#: templates/devapp/custom_dev_page/olt.html:19 +#: templates/devapp/custom_dev_page/onu.html:19 +#: templates/devapp/custom_dev_page/onu_for_zte.html:19 msgid "Mac" msgstr "Мак" -#: devapp/templates/devapp/custom_dev_page/olt.html:20 -#: devapp/templates/devapp/custom_dev_page/onu.html:73 +#: templates/devapp/custom_dev_page/olt.html:20 +#: templates/devapp/custom_dev_page/onu.html:74 +#: templates/devapp/custom_dev_page/onu_for_zte.html:77 msgid "Signal" msgstr "Ур. сигнала" -#: devapp/templates/devapp/custom_dev_page/olt.html:38 +#: templates/devapp/custom_dev_page/olt.html:38 +#: templates/devapp/custom_dev_page/olt_ztec320_ports.html:45 msgid "Create device" msgstr "Создать устройство" -#: devapp/templates/devapp/custom_dev_page/olt.html:45 -#: devapp/templates/devapp/manage_ports/list.html:48 +#: templates/devapp/custom_dev_page/olt.html:45 +#: templates/devapp/manage_ports/list.html:48 msgid "Ports not found" msgstr "Порты не найдены" -#: devapp/templates/devapp/custom_dev_page/onu.html:10 -#: devapp/templates/devapp/custom_dev_page/ports.html:10 +#: templates/devapp/custom_dev_page/olt_ztec320.html:31 +#: templates/devapp/custom_dev_page/ports.html:54 +msgid "We have not received info, please check options :(" +msgstr "Инфа не получена, проверьте настройки :(" + +#: templates/devapp/custom_dev_page/olt_ztec320.html:36 +msgid "Long description" +msgstr "Длинное описание" + +#: templates/devapp/custom_dev_page/olt_ztec320.html:37 +msgid "Hostname" +msgstr "Имя хоста" + +#: templates/devapp/custom_dev_page/olt_ztec320_ports.html:15 +msgid "OLT Scan" +msgstr "Скан OLT" + +#: templates/devapp/custom_dev_page/olt_ztec320_ports.html:27 +msgid "Onu type" +msgstr "Тип ONU" + +#: templates/devapp/custom_dev_page/olt_ztec320_ports.html:28 +msgid "Onu port" +msgstr "Порт ONU" + +#: templates/devapp/custom_dev_page/olt_ztec320_ports.html:29 +msgid "Onu signal" +msgstr "Сигнала ONU" + +#: templates/devapp/custom_dev_page/olt_ztec320_ports.html:30 +msgid "Serial" +msgstr "Серийный номер" + +#: templates/devapp/custom_dev_page/olt_ztec320_ports.html:52 +msgid "ONU not found" +msgstr "ONU не найдена" + +#: templates/devapp/custom_dev_page/onu.html:10 +#: templates/devapp/custom_dev_page/onu_for_zte.html:10 +#: templates/devapp/custom_dev_page/ports.html:10 msgid "Title of the type of switch" msgstr "Название типа свича" -#: devapp/templates/devapp/custom_dev_page/onu.html:22 +#: templates/devapp/custom_dev_page/onu.html:22 +#: templates/devapp/custom_dev_page/onu_for_zte.html:22 msgid "Attached user" msgstr "Прикрепленный абонент" -#: devapp/templates/devapp/custom_dev_page/onu.html:47 +#: templates/devapp/custom_dev_page/onu.html:48 +#: templates/devapp/custom_dev_page/onu_for_zte.html:50 msgid "ONU Status" msgstr "Состояние ONU" -#: devapp/templates/devapp/custom_dev_page/onu.html:57 +#: templates/devapp/custom_dev_page/onu.html:58 +#: templates/devapp/custom_dev_page/onu_for_zte.html:60 msgid "ONU error" msgstr "ONU ошибка" -#: devapp/templates/devapp/custom_dev_page/onu.html:71 +#: templates/devapp/custom_dev_page/onu.html:72 +#: templates/devapp/custom_dev_page/onu_for_zte.html:75 msgid "Name on OLT" msgstr "Имя на OLT" -#: devapp/templates/devapp/custom_dev_page/onu.html:72 +#: templates/devapp/custom_dev_page/onu.html:73 +#: templates/devapp/custom_dev_page/onu_for_zte.html:76 msgid "Distance(m)" msgstr "Расстояние (м)" -#: devapp/templates/devapp/custom_dev_page/onu.html:77 -#: devapp/templates/devapp/custom_dev_page/onu.html:85 +#: templates/devapp/custom_dev_page/onu.html:78 +#: templates/devapp/custom_dev_page/onu.html:86 msgid "Mac on OLT" msgstr "MAC адрес на OLT" -#: devapp/templates/devapp/custom_dev_page/onu.html:78 +#: templates/devapp/custom_dev_page/onu.html:79 msgid "Mac-addresses does not match" msgstr "MAC адреса не совпадают" -#: devapp/templates/devapp/custom_dev_page/onu.html:79 -#: devapp/templates/devapp/custom_dev_page/onu.html:81 +#: templates/devapp/custom_dev_page/onu.html:80 +#: templates/devapp/custom_dev_page/onu.html:82 msgid "Fix it" msgstr "Исправить" -#: devapp/templates/devapp/custom_dev_page/onu.html:92 +#: templates/devapp/custom_dev_page/onu.html:93 +#: templates/devapp/custom_dev_page/onu_for_zte.html:83 msgid "Info does not fetch" msgstr "Информация не получена" -#: devapp/templates/devapp/custom_dev_page/ports.html:44 +#: templates/devapp/custom_dev_page/ports.html:44 msgid "Disable port" msgstr "Выключить порт" -#: devapp/templates/devapp/custom_dev_page/ports.html:48 +#: templates/devapp/custom_dev_page/ports.html:48 msgid "Enable port" msgstr "Включить порт" -#: devapp/templates/devapp/custom_dev_page/ports.html:54 -msgid "We have not received info, please check options :(" -msgstr "Инфа не получена, проверьте настройки :(" - -#: devapp/templates/devapp/custom_dev_page/ports.html:80 +#: templates/devapp/custom_dev_page/ports.html:80 msgid "Device log" msgstr "Лог устройства" -#: devapp/templates/devapp/custom_dev_page/ports.html:87 +#: templates/devapp/custom_dev_page/ports.html:87 msgid "Level" msgstr "Уровень" -#: devapp/templates/devapp/custom_dev_page/ports.html:89 +#: templates/devapp/custom_dev_page/ports.html:89 msgid "Date" msgstr "Дата" -#: devapp/templates/devapp/custom_dev_page/ports.html:104 +#: templates/devapp/custom_dev_page/ports.html:104 msgid "Ports comment" msgstr "Комментарии портов" -#: devapp/templates/devapp/custom_dev_page/ports.html:111 +#: templates/devapp/custom_dev_page/ports.html:111 msgid "Title" msgstr "Название" -#: devapp/templates/devapp/custom_dev_page/ports.html:122 +#: templates/devapp/custom_dev_page/ports.html:122 msgid "We have not received info for ports" msgstr "Инфа о портах не получена" -#: devapp/templates/devapp/dev.html:65 devapp/templates/devapp/dev.html:69 -#: devapp/templates/devapp/manage_ports/add_ports.html:53 -#: devapp/templates/devapp/manage_ports/add_ports.html:55 -#: devapp/templates/devapp/manage_ports/list.html:35 -#: devapp/templates/devapp/manage_ports/modal_del_port.html:14 +#: templates/devapp/dev.html:65 templates/devapp/dev.html:69 +#: templates/devapp/manage_ports/add_ports.html:53 +#: templates/devapp/manage_ports/add_ports.html:55 +#: templates/devapp/manage_ports/list.html:35 +#: templates/devapp/manage_ports/modal_del_port.html:14 msgid "Delete" msgstr "Удалить" -#: devapp/templates/devapp/dev.html:68 +#: templates/devapp/dev.html:68 msgid "Permission denied" msgstr "Доступ запрещён" -#: devapp/templates/devapp/devices.html:81 -#: devapp/templates/devapp/devices_null_group.html:62 +#: templates/devapp/device_confirm_delete.html:9 +msgid "Remove device" +msgstr "Удалить устройство" + +#: templates/devapp/device_confirm_delete.html:13 +msgid "Are you sure you want to delete device?" +msgstr "Вы уверены что хотите удалить устройство?" + +#: templates/devapp/devices.html:81 templates/devapp/devices_null_group.html:62 msgid "Devices does not found" msgstr "Нет созданных устройств" -#: devapp/templates/devapp/devices.html:81 -#: devapp/templates/devapp/devices.html:91 -#: devapp/templates/devapp/devices_null_group.html:62 -#: devapp/templates/devapp/devices_null_group.html:72 +#: templates/devapp/devices.html:81 templates/devapp/devices.html:91 +#: templates/devapp/devices_null_group.html:62 +#: templates/devapp/devices_null_group.html:72 msgid "Create" msgstr "Cоздать" -#: devapp/templates/devapp/devices_null_group.html:13 -#: devapp/templates/devapp/group_list.html:32 +#: templates/devapp/devices_null_group.html:13 +#: templates/devapp/group_list.html:32 msgid "Devices without group" msgstr "Устройства без группы" -#: devapp/templates/devapp/fix_dev_group.html:17 +#: templates/devapp/fix_dev_group.html:17 msgid "Fix device group" msgstr "Поправить группу устройства" -#: devapp/templates/devapp/group_list.html:14 +#: templates/devapp/group_list.html:14 msgid "Group title" msgstr "Название" -#: devapp/templates/devapp/group_list.html:24 +#: templates/devapp/group_list.html:24 msgid "Groups was not found" msgstr "Эта группа не найдена" -#: devapp/templates/devapp/group_list.html:35 +#: templates/devapp/group_list.html:35 msgid "Export to nagios objects" msgstr "Экспортировать конфиг для nagios" -#: devapp/templates/devapp/manage_ports/add_ports.html:10 -#: devapp/templates/devapp/manage_ports/list.html:58 +#: templates/devapp/manage_ports/add_ports.html:10 +#: templates/devapp/manage_ports/list.html:58 msgid "Add ports" msgstr "Добавить порты" -#: devapp/templates/devapp/manage_ports/add_ports.html:32 +#: templates/devapp/manage_ports/add_ports.html:32 msgid "Mode" msgstr "Режим" -#: devapp/templates/devapp/manage_ports/add_ports.html:60 -#: devapp/templates/devapp/manage_ports/list.html:57 +#: templates/devapp/manage_ports/add_ports.html:60 +#: templates/devapp/manage_ports/list.html:57 msgid "Add" msgstr "Добавить" -#: devapp/templates/devapp/manage_ports/fix_abon_device.html:10 -#: devapp/templates/devapp/manage_ports/fix_abon_device.html:16 +#: templates/devapp/manage_ports/fix_abon_device.html:10 +#: templates/devapp/manage_ports/fix_abon_device.html:16 msgid "Fix subscriber ports conflict" msgstr "Исправить конфликт абонентов на порту" -#: devapp/templates/devapp/manage_ports/fix_abon_device.html:21 +#: templates/devapp/manage_ports/fix_abon_device.html:21 msgid "Subscribers list on port" msgstr "Список абонентов на порту" -#: devapp/templates/devapp/manage_ports/fix_abon_device.html:25 +#: templates/devapp/manage_ports/fix_abon_device.html:25 msgid "" "You may choose the subscriber who correctly attached to device port. When " "you have found right subscriber, remove the port from the other person" @@ -397,15 +476,15 @@ msgstr "" "найдёте нужного абонента удалите этот порт у другого абонента чтоб исправить " "конфликт" -#: devapp/templates/devapp/manage_ports/fix_abon_device.html:31 +#: templates/devapp/manage_ports/fix_abon_device.html:31 msgid "Abons not found" msgstr "Абоненты не найдены" -#: devapp/templates/devapp/manage_ports/list.html:13 +#: templates/devapp/manage_ports/list.html:13 msgid "Count of subscribers" msgstr "Сколько абонов" -#: devapp/templates/devapp/manage_ports/list.html:26 +#: templates/devapp/manage_ports/list.html:26 #, python-format msgid "" "Port should not have more than one subscriber, fix thatисправить" -#: devapp/templates/devapp/manage_ports/list.html:31 +#: templates/devapp/manage_ports/list.html:31 msgid "Show subscriber on port" msgstr "Показать абонента на порту" -#: devapp/templates/devapp/manage_ports/list.html:40 -#: devapp/templates/devapp/manage_ports/modal_add_edit_port.html:14 +#: templates/devapp/manage_ports/list.html:40 +#: templates/devapp/manage_ports/modal_add_edit_port.html:14 msgid "Edit" msgstr "Редактировать" -#: devapp/templates/devapp/manage_ports/modal_del_port.html:7 +#: templates/devapp/manage_ports/modal_del_port.html:7 msgid "Are you sure?" msgstr "Вы уверены?" -#: devapp/templates/devapp/manage_ports/modal_del_port.html:11 +#: templates/devapp/manage_ports/modal_del_port.html:11 msgid "Are you sure that you want to delete switch port from db?" msgstr "Вы уверены что хотите удалить порт свича из бд?" -#: devapp/templates/devapp/manage_ports/modal_show_subscriber_on_port.html:5 +#: templates/devapp/manage_ports/modal_show_subscriber_on_port.html:5 msgid "Subscriber on port" msgstr "Абонент на порту" -#: devapp/views.py:80 -msgid "Delete failed" -msgstr "Неизвестная ошибка при удалении :(" +#: views.py:87 +msgid "Device successfully deleted" +msgstr "Устройство успешно удалено" -#: devapp/views.py:109 +#: views.py:115 msgid "You have redirected to existing device" msgstr "Вы были переадресованы на существующее устройство" -#: devapp/views.py:112 devapp/views.py:361 devapp/views.py:457 +#: views.py:118 views.py:368 views.py:481 msgid "Please attach group for device" msgstr "Пожалуйста назначте устройству группу в настройках" -#: devapp/views.py:121 +#: views.py:127 msgid "Device info has been saved" msgstr "Инфа о точке сохранена" -#: devapp/views.py:124 devapp/views.py:307 devapp/views.py:336 -#: devapp/views.py:459 +#: views.py:130 views.py:314 views.py:343 views.py:483 msgid "Form is invalid, check fields and try again" msgstr "Ошибка в данных, проверте их ещё раз" -#: devapp/views.py:127 +#: views.py:133 #, python-format msgid "Duplicate user and port: %s" msgstr "Пользователь с таким портом и устройством уже есть: %s" -#: devapp/views.py:168 devapp/views.py:230 -msgid "Device does not have a group, please fix that" -msgstr "У устройства нет группы, пожалуйста, исправьте это" - -#: devapp/views.py:173 devapp/views.py:263 devapp/views.py:348 +#: views.py:180 views.py:270 views.py:355 msgid "Device does not exist" msgstr "Устойство не найдено" -#: devapp/views.py:194 +#: views.py:201 msgid "Subscribers on port does not exist" msgstr "Абоненты на порту не найдены" -#: devapp/views.py:196 +#: views.py:203 msgid "More than one subscriber on device port" msgstr "Больше одного абонента на порту устройства" -#: devapp/views.py:282 +#: views.py:289 msgid "Port successfully removed" msgstr "Порт успешно удалён" -#: devapp/views.py:290 devapp/views.py:318 +#: views.py:297 views.py:325 msgid "Port does not exist" msgstr "Порт не найден" -#: devapp/views.py:305 devapp/views.py:333 +#: views.py:312 views.py:340 msgid "Port successfully saved" msgstr "Порт успешно сохранён" -#: devapp/views.py:368 devapp/views.py:411 +#: views.py:375 views.py:431 msgid "Dot was not pinged" msgstr "Эта точка не пингуется" -#: devapp/views.py:377 devapp/views.py:409 +#: views.py:384 views.py:429 msgid "Not Set snmp device password" msgstr "Не указан snmp пароль для устройства" -#: devapp/views.py:385 +#: views.py:392 msgid "SNMP error on device" msgstr "Ошибка SNMP на устройстве" -#: devapp/views.py:454 +#: views.py:478 msgid "Device fixed" msgstr "Устройство исправлено" -#: devapp/views.py:483 +#: views.py:508 #, python-format msgid "Device with mac address %(mac)s does not exist" msgstr "Устройство мак адресом %(mac)s не найдено" -#: devapp/views.py:491 +#: views.py:516 msgid "Fixed" msgstr "Исправлено, обновите страницу" -#: devapp/views.py:494 +#: views.py:519 msgid "Parent device not found" msgstr "Вышестоящее устройство не найдено" -#~ msgid "Plugin output" -#~ msgstr "Вывод мониторинга" - msgid "Device %(device_name)s is up" msgstr "%(device_name)s в сети" @@ -532,15 +603,3 @@ msgstr "%(device_name)s недостижим" msgid "Device %(device_name)s getting undefined status code" msgstr "Устройство %(device_name)s получило не определённый код состояния" - -msgid "Ip address or parent device with ip address required for ONU device" -msgstr "Необходим ip адрес. Или родительское устройство с назначенным ip адресом необходимо." - -msgid "Are you sure you want to delete device?" -msgstr "Вы уверены что хотите удалить устройство?" - -msgid "Remove device" -msgstr "Удалить устройство" - -msgid "Device successfully deleted" -msgstr "Устройство успешно удалено" diff --git a/devapp/models.py b/devapp/models.py index 24e5066..4dddfa5 100644 --- a/devapp/models.py +++ b/devapp/models.py @@ -20,6 +20,8 @@ class DeviceMonitoringException(Exception): class Device(models.Model): + _cached_manager = None + ip_address = MyGenericIPAddressField(verbose_name=_('Ip address'), null=True, blank=True) mac_addr = MACAddressField(verbose_name=_('Mac address'), null=True, blank=True, unique=True) comment = models.CharField(_('Comment'), max_length=256) @@ -71,11 +73,14 @@ class Device(models.Model): res = klasses[0][1] if issubclass(res, DevBase): return res - return + raise TypeError('one of types is not subclass of DevBase. ' + 'Or implementation of that device type is not found') def get_manager_object(self) -> DevBase: man_klass = self.get_manager_klass() - return man_klass(self) + if self._cached_manager is None: + self._cached_manager = man_klass(self) + return self._cached_manager # Can attach device to subscriber in subscriber page def has_attachable_to_subscriber(self): diff --git a/devapp/templates/devapp/devices.html b/devapp/templates/devapp/devices.html index 248ae43..8e75e2a 100644 --- a/devapp/templates/devapp/devices.html +++ b/devapp/templates/devapp/devices.html @@ -30,7 +30,6 @@ {% if order_by == 'comment' %}{% endif %} {% trans 'Mac address' %} - {# {% trans 'Plugin output' %} #} {% trans 'Device type' %} @@ -61,7 +60,6 @@ {{ dev.ip_address }} {{ dev.comment }} {{ dev.mac_addr|default:_('Not assigned') }} - {# {{ dev.mon.plugin_output|default:'–' }} #} {{ dev.get_devtype_display }} {% if can_del_dev %} diff --git a/djing/lib/__init__.py b/djing/lib/__init__.py index fc0e76d..d358041 100644 --- a/djing/lib/__init__.py +++ b/djing/lib/__init__.py @@ -1,5 +1,6 @@ import socket import struct +from abc import ABCMeta from datetime import timedelta from collections import Iterator from django.db import models @@ -125,12 +126,10 @@ class LogicError(Exception): pass -def singleton(class_): - instances = {} +class Singleton(type): + _instances = {} - def getinstance(*args, **kwargs): - if class_ not in instances: - instances[class_] = class_(*args, **kwargs) - return instances[class_] - - return getinstance + def __call__(cls, *args, **kwargs): + if cls not in cls._instances: + cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) + return cls._instances[cls] diff --git a/djing/lib/tln/__init__.py b/djing/lib/tln/__init__.py new file mode 100644 index 0000000..5f19e0c --- /dev/null +++ b/djing/lib/tln/__init__.py @@ -0,0 +1,4 @@ +from .tln import * + +__all__ = ('TelnetApi', 'ValidationError', 'ZTEFiberNumberNotFound', 'ZTEFiberIsFull', + 'OnuZteRegisterError', 'ZteOltConsoleError', 'register_onu_ZTE_F660') diff --git a/djing/lib/tln/tln.py b/djing/lib/tln/tln.py new file mode 100755 index 0000000..fa52217 --- /dev/null +++ b/djing/lib/tln/tln.py @@ -0,0 +1,266 @@ +#!/usr/bin/env python3 +import re +import struct +from telnetlib import Telnet +from time import sleep +from typing import Generator, Dict, Optional, Tuple + + +class ZteOltConsoleError(Exception): + pass + + +class OnuZteRegisterError(ZteOltConsoleError): + pass + + +class ZTEFiberIsFull(ZteOltConsoleError): + pass + + +class ZTEFiberNumberNotFound(ZteOltConsoleError): + pass + + +class ValidationError(ValueError): + pass + + +MAC_ADDR_REGEX = b'^([0-9A-Fa-f]{1,2}[:-]){5}([0-9A-Fa-f]{1,2})$' +IP_ADDR_REGEX = '^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.' \ + '(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.' \ + '(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.' \ + '(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$' +ONU_SN_REGEX = b'^ZTEG[A-F\d]{8}$' + + +class TelnetApi(Telnet): + config_level = [] + + def __init__(self, *args, **kwargs): + timeout = kwargs.get('timeout') + if timeout: + self._timeout = timeout + self._prompt_string = b'ZTE-C320-PKP#' + super().__init__(*args, **kwargs) + + def write(self, buffer: bytes) -> None: + buffer = buffer + b'\n' + print('>>', buffer) + super().write(buffer) + + def resize_screen(self, width: int, height: int): + naws_cmd = struct.pack('>BBBHHBB', + 255, 250, 31, # IAC SB NAWS + width, height, + 255, 240 # IAC SE + ) + sock = self.get_socket() + sock.send(naws_cmd) + + def enter(self, username: bytes, passw: bytes) -> None: + self.read_until(b'Username:') + self.write(username) + self.read_until(b'Password:') + self.write(passw) + + def read_lines(self) -> Generator: + while True: + line = self.read_until(b'\r\n', timeout=self._timeout) + line = line.replace(b'\r\n', b'') + if self._prompt_string == line: + break + if line == b'': + continue + yield line + + def command_to(self, cmd: bytes) -> Generator: + self.write(cmd) + return self.read_lines() + + def set_prompt_string(self, prompt_string: bytes) -> None: + self.config_level.append(prompt_string) + self._prompt_string = prompt_string + + def level_exit(self) -> Optional[Tuple]: + if len(self.config_level) < 2: + print('We are in root') + return + self.config_level.pop() + self.set_prompt_string(self.config_level[-1]) + return tuple(self.command_to(b'exit')) + + def __del__(self): + if self.sock: + self.write(b'exit') + super().__del__() + + +def parse_onu_name(onu_name: bytes, name_regexp=re.compile(b'[/:_]')) -> Dict[str, bytes]: + gpon_onu, stack_num, rack_num, fiber_num, onu_num = name_regexp.split(onu_name) + return { + 'stack_num': stack_num, + 'rack_num': rack_num, + 'fiber_num': fiber_num, + 'onu_num': onu_num + } + + +class OltZTERegister(TelnetApi): + + def __init__(self, screen_size: Tuple[int, int], *args, **kwargs): + super().__init__(*args, **kwargs) + self.resize_screen(*screen_size) + + def get_unregistered_onu(self, sn: bytes) -> Optional[Dict]: + lines = tuple(self.command_to(b'show gpon onu uncfg')) + if len(lines) > 3: + # devices available + # find onu by sn + line = tuple(ln for ln in lines if sn.lower() in ln.lower()) + if len(line) > 0: + line = line[0] + onu_name, onu_sn, onu_state = line.split() + onu_numbers = parse_onu_name(onu_name) + onu_numbers.update({ + 'onu_name': onu_name, + 'onu_sn': onu_sn, + 'onu_state': onu_state + }) + return onu_numbers + + def get_last_registered_onu_number(self, stack_num: int, rack_num: int, fiber_num: int) -> int: + registered_lines = self.command_to(b'show run int gpon-olt_%d/%d/%d' % ( + stack_num, + rack_num, + fiber_num + )) + onu_type_regexp = re.compile(b'^\s{2}onu \d{1,3} type [-\w\d]{4,64} sn \w{4,64}$') + last_onu = 0 + for rl in registered_lines: + if rl == b' --More--': + self.write(b' ') + if onu_type_regexp.match(rl): + _onu, num, _type, onu_type, _sn, onu_sn = rl.split() + last_onu = int(num) + return last_onu + + def enter_to_config_mode(self) -> bool: + prompt = b'ZTE-C320-PKP(config)#' + self.set_prompt_string(prompt) + res = tuple(self.command_to(b'config terminal')) + if res[1].startswith(b'Enter configuration commands'): + # ok, we in the config mode + return True + return False + + def go_to_olt_interface(self, stack_num: int, rack_num: int, fiber_num: int) -> Tuple: + self.set_prompt_string(b'ZTE-C320-PKP(config-if)#') + return tuple(self.command_to(b'interface gpon-olt_%d/%d/%d' % ( + stack_num, + rack_num, + fiber_num + ))) + + def go_to_onu_interface(self, stack_num: int, rack_num: int, fiber_num: int, onu_port_num: int) -> Tuple: + self.set_prompt_string(b'ZTE-C320-PKP(config-if)#') + return tuple(self.command_to(b'interface gpon-onu_%d/%d/%d:%d' % ( + stack_num, + rack_num, + fiber_num, + onu_port_num + ))) + + def apply_conf_to_onu(self, mac_addr: bytes, vlan_id: int) -> None: + tmpl = ( + b'switchport vlan %d tag vport 1' % vlan_id, + b'port-location format flexible-syntax vport 1', + b'port-location sub-option remote-id enable vport 1', + b'port-location sub-option remote-id name %s vport 1' % mac_addr, + b'dhcp-option82 enable vport 1', + b'dhcp-option82 trust true replace vport 1', + b'ip dhcp snooping enable vport 1' + ) + for conf_line in tmpl: + self.write(conf_line) + + def register_onu_on_olt_fiber(self, onu_type: bytes, new_onu_num: int, onu_sn: bytes, line_profile: bytes, + remote_profile: bytes) -> Tuple: + # ok, we in interface + tpl = b'onu %d type %s sn %s' % (new_onu_num, onu_type, onu_sn) + r = tuple(self.command_to(tpl)) + return tuple(self.command_to(b'onu %d profile line %s remote %s' % ( + new_onu_num, + line_profile, + remote_profile + ))) + r + + +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' + remote_profile = b'ZTE-F660-ROUTER' + if not re.match(MAC_ADDR_REGEX, onu_mac): + raise ValidationError + if not re.match(IP_ADDR_REGEX, olt_ip): + raise ValidationError + if not re.match(ONU_SN_REGEX, onu_sn): + raise ValidationError + + tn = OltZTERegister(host=olt_ip, timeout=2, screen_size=(120, 128)) + tn.enter(*login_passwd) + + unregistered_onu = tn.get_unregistered_onu(onu_sn) + if unregistered_onu is None: + raise OnuZteRegisterError('unregistered onu not found, sn=%s' % onu_sn.decode('utf-8')) + + stack_num = int(unregistered_onu['stack_num']) + rack_num = int(unregistered_onu['rack_num']) + fiber_num = int(unregistered_onu['fiber_num']) + + last_onu_number = tn.get_last_registered_onu_number( + stack_num, rack_num, fiber_num + ) + + if last_onu_number < 1: + raise ZTEFiberNumberNotFound + elif last_onu_number > 126: + raise ZTEFiberIsFull('olt fiber %d is full' % fiber_num) + + # enter to config + if not tn.enter_to_config_mode(): + raise ZteOltConsoleError('Failed to enter to config mode') + + # go to olt interface + if not tn.go_to_olt_interface(stack_num, rack_num, fiber_num): + raise ZteOltConsoleError('Failed to enter in olt fiber port') + + # new onu port number + new_onu_port_num = last_onu_number + 1 + + # register onu on olt interface + r = tn.register_onu_on_olt_fiber(onu_type, new_onu_port_num, onu_sn, line_profile, remote_profile) + print(r) + + # exit from olt interface + tn.level_exit() + + r = tn.go_to_onu_interface(stack_num, rack_num, fiber_num, new_onu_port_num) + print(r) + + tn.apply_conf_to_onu(onu_mac, 145) + sleep(1) + return + + +if __name__ == '__main__': + ip = '10.40.1.10' + try: + register_onu_ZTE_F660( + olt_ip=ip, onu_sn=b'ZTEGC0458DCE', login_passwd=(b'admin', b'2ekc3'), + onu_mac=b'cc:7b:35:8b:7:0' + ) + except ZteOltConsoleError as e: + print(e) + except ConnectionRefusedError: + print('ERROR: connection refused', ip)