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') } 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/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 %} -
+{% block modal_form_title %} + {% trans 'Remove subscriber' %} +{% endblock %} diff --git a/abonapp/views.py b/abonapp/views.py index 5139d2a..bd3215a 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.decorators.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.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, mydefs.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 @@ -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.decorators.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.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, mydefs.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 -@mydefs.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, mydefs.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 @@ -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.decorators.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.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): @@ -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.decorators.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.decorators.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.decorators.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.decorators.only_admins @json_view def abons(request): ablist = ({ @@ -1145,7 +1144,7 @@ def abons(request): @login_required -@mydefs.only_admins +@lib.decorators.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..9f3bbbd 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,8 @@ from group_app.models import Group from .models import UserProfile from .forms import AvatarChangeForm -import mydefs +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 @@ -62,9 +62,9 @@ class SignOut(RedirectView): @login_required -@mydefs.only_admins +@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 +87,7 @@ def profile_show(request, uid=0): }) -@method_decorator((login_required, mydefs.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' @@ -100,7 +100,7 @@ class AvatarUpdateView(UpdateView): @login_required -@mydefs.only_admins +@only_admins def ch_info(request): if request.method == 'POST': user = request.user @@ -169,7 +169,7 @@ def create_profile(request): @login_required -@mydefs.only_admins +@only_admins def delete_profile(request, uid): prf = get_object_or_404(UserProfile, id=uid) if uid != request.user.id: @@ -180,7 +180,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, only_admins), name='dispatch') class AccountsListView(BaseAccListView): template_name = 'accounts/acc_list.html' context_object_name = 'users' @@ -288,7 +288,7 @@ def set_abon_groups_permission(request, uid): }) -@method_decorator((login_required, mydefs.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/agent/mod_mikrotik.py b/agent/mod_mikrotik.py index 7d722ed..5a53711 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 @@ -19,8 +19,7 @@ LIST_USERS_ALLOWED = 'DjingUsersAllowed' #LIST_USERS_BLOCKED = 'DjingUsersBlocked' -@singleton -class ApiRos: +class ApiRos(metaclass=Singleton): """Routeros api""" sk = None is_login = False @@ -224,8 +223,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( @@ -483,7 +482,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/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/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 7dda24e..a785b79 100644 --- a/devapp/dev_types.py +++ b/devapp/dev_types.py @@ -1,9 +1,10 @@ -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 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): @@ -197,7 +206,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: @@ -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): @@ -272,8 +288,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 +312,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 +321,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 +345,42 @@ 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' + + 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 b9f1eef..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" @@ -414,113 +493,105 @@ msgstr "" "Порт не должен иметь больше одного назначенного абонента, исправить" -#: 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,6 +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 адресом необходимо." 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/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..4dddfa5 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 @@ -19,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) @@ -27,7 +30,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 +40,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')), @@ -69,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/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 @@{% 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