Browse Source

Merge branch 'devel' of https://github.com/nerosketch/djing into devel

devel
www-data 8 years ago
parent
commit
eb33f6ad65
  1. 8
      abonapp/forms.py
  2. 4
      abonapp/migrations/0001_initial.py
  3. 2
      abonapp/models.py
  4. 2
      abonapp/pay_systems.py
  5. 22
      abonapp/templates/abonapp/abon_confirm_delete.html
  6. 100
      abonapp/views.py
  7. 18
      accounts_app/views.py
  8. 9
      agent/mod_mikrotik.py
  9. 34
      agent/structs.py
  10. 2
      clientsideapp/views.py
  11. 15
      devapp/base_intr.py
  12. 90
      devapp/dev_types.py
  13. 18
      devapp/forms.py
  14. 424
      devapp/locale/ru/LC_MESSAGES/django.po
  15. 4
      devapp/migrations/0001_initial.py
  16. 4
      devapp/migrations/0002_auto_20180409_1318.py
  17. 60
      devapp/migrations/0003_auto_20180529_1311.py
  18. 17
      devapp/models.py
  19. 2
      devapp/templates/devapp/add_dev.html
  20. 2
      devapp/templates/devapp/custom_dev_page/olt_ztec320_ports.html
  21. 17
      devapp/templates/devapp/custom_dev_page/onu.html
  22. 91
      devapp/templates/devapp/custom_dev_page/onu_for_zte.html
  23. 4
      devapp/templates/devapp/dev.html
  24. 14
      devapp/templates/devapp/device_confirm_delete.html
  25. 4
      devapp/templates/devapp/devices.html
  26. 4
      devapp/templates/devapp/devices_null_group.html
  27. 2
      devapp/urls.py
  28. 45
      devapp/views.py
  29. 2
      dialing.py
  30. 3
      dialing_app/views.py
  31. 70
      djing/lib/__init__.py
  32. 0
      djing/lib/auth_backends.py
  33. 35
      djing/lib/decorators.py
  34. 0
      djing/lib/messaging/__init__.py
  35. 7
      djing/lib/messaging/sms/__init__.py
  36. 0
      djing/lib/messaging/sms/base.py
  37. 0
      djing/lib/messaging/sms/consts.py
  38. 8
      djing/lib/messaging/sms/deliver.py
  39. 0
      djing/lib/messaging/sms/gsm0338.py
  40. 0
      djing/lib/messaging/sms/pdu.py
  41. 10
      djing/lib/messaging/sms/submit.py
  42. 0
      djing/lib/messaging/sms/udh.py
  43. 2
      djing/lib/messaging/sms/wap.py
  44. 0
      djing/lib/messaging/utils.py
  45. 4
      djing/lib/tln/__init__.py
  46. 266
      djing/lib/tln/tln.py
  47. 2
      djing/settings.py
  48. 2
      docs/dev.md
  49. 23
      group_app/templates/group_app/group_confirm_delete.html
  50. 2
      mapapp/views.py
  51. 7
      messaging/sms/__init__.py
  52. 359
      migrate_to_0.2.py
  53. 2
      periodic.py
  54. 18
      static/css/custom.css
  55. 5
      statistics/migrations/0001_initial.py
  56. 2
      statistics/models.py
  57. 4
      tariff_app/base_intr.py
  58. 3
      tariff_app/locale/ru/LC_MESSAGES/django.po
  59. 2
      tariff_app/models.py
  60. 30
      tariff_app/templates/tariff_app/modal_del_warning.html
  61. 15
      tariff_app/templates/tariff_app/tariff_confirm_delete.html
  62. 2
      tariff_app/urls.py
  63. 36
      tariff_app/views.py
  64. 2
      taskapp/handle.py
  65. 2
      taskapp/migrations/0001_initial.py
  66. 8
      taskapp/views.py
  67. 19
      templates/base_delete_modal.html

8
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')
}

4
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)),

2
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

2
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

22
abonapp/templates/abonapp/abon_confirm_delete.html

@ -1,18 +1,10 @@
{% extends 'base_delete_modal.html' %}
{% load i18n %}
<form role="form" action="{% url 'abonapp:del_abon' abon.group.pk abon.username %}" method="post">{% csrf_token %}
<div class="modal-header warning">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title"><span class="glyphicon glyphicon-earphone"></span>{% trans 'Remove subscriber' %}</h4>
</div>
<div class="modal-body">
{% block modal_form_url %}
{% url 'abonapp:del_abon' abon.group.pk abon.username %}
{% endblock %}
<h4>{% trans 'Are you sure about them?' %}</h4>
<button type="submit" class="btn btn-danger" value="DELETE">
<span class="glyphicon glyphicon-remove"></span> {% trans 'Delete' %}
</button>
</div>
</form>
{% block modal_form_title %}
{% trans 'Remove subscriber' %}
{% endblock %}

100
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)
@ -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 = '<span class="glyphicon glyphicon-exclamation-sign"></span> %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 = '<span class="glyphicon glyphicon-ok"></span> %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

18
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'

9
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(

34
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

2
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

15
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):

90
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'))

18
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,

424
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 "&lt;Не назначено&gt;"
#: 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, <a href='%(furl)s'>fix that</"
@ -414,113 +493,105 @@ msgstr ""
"Порт не должен иметь больше одного назначенного абонента, <a "
"href='%(furl)s'>исправить</a>"
#: 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 адресом необходимо."

4
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')),

4
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'),
),
]

60
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)
]

17
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):

2
devapp/templates/devapp/add_dev.html

@ -63,7 +63,7 @@
</div>
{% 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 %}

2
devapp/templates/devapp/custom_dev_page/olt_ztec320_ports.html

@ -42,7 +42,7 @@
<td>{{ onu.onu_signal }}</td>
<td>{{ onu.onu_sn }}</td>
<td>
<a href="{% url 'devapp:add' grp %}?t=On&pdev={{ dev.pk }}&c={{ onu.onu_sn }}" title="{% trans 'Create device' %}">
<a href="{% url 'devapp:add' grp %}?t=Zo&pdev={{ dev.pk }}&c={{ onu.onu_sn }}&n={{ onu.snmp_extra }}" title="{% trans 'Create device' %}">
<span class="glyphicon glyphicon-plus"></span>
</a>
</td>

17
devapp/templates/devapp/custom_dev_page/onu.html

@ -31,9 +31,10 @@
{% if dev.parent_dev %}
<li class="list-group-item">
{% with pdev=dev.parent_dev pdgrp=dev.parent_dev.group %}
{% trans 'Parent device' %}:<a href="{% url 'devapp:view' pdgrp.pk pdev.pk %}"
title="{{ pdev.mac_addr|default:'' }}"
target="_blank">{{ pdev.ip_address|default:'-' }} {{ pdev.comment }}</a>
{% trans 'Parent device' %}:
<a href="{% url 'devapp:view' pdgrp.pk pdev.pk %}" title="{{ pdev.mac_addr|default:'' }}" target="_blank">
{{ pdev.ip_address|default:'-' }} {{ pdev.comment }}
</a>
{% endwith %}
</li>
{% endif %}
@ -51,20 +52,20 @@
{% if onu_details %}
{% if onu_details.err %}
<div class="media">
<div class="media-left"><span class="media-object glyphicon glyphicon-remove-sign text-danger" style="font-size: 75px;"></span></div>
<div class="media-left"><span class="media-object glyphicon glyphicon-remove-sign text-danger font-extra-large"></span></div>
</div>
<div class="media-body">
<b>{% trans 'ONU error' %}</b>: {{ onu_details.err }}<br>
</div>
{% else %}
<div class="media">
<div class="media-left">
<div class="media-left font-extra-large">
{% if onu_details.status == '3' %}
<span class="media-object glyphicon glyphicon-ok-sign text-success" style="font-size: 75px;"></span>
<span class="media-object glyphicon glyphicon-ok-sign text-success"></span>
{% elif onu_details.status == '2' %}
<span class="media-object glyphicon glyphicon-remove-sign text-danger" style="font-size: 75px;"></span>
<span class="media-object glyphicon glyphicon-remove-sign text-danger"></span>
{% else %}
<span class="media-object glyphicon glyphicon-question-sign" style="font-size: 75px;"></span>
<span class="media-object glyphicon glyphicon-question-sign"></span>
{% endif %}
</div>
<div class="media-body">

91
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 %}
<div class="row">
<div class="col-xs-12 col-sm-6">
<div class="panel panel-default">
<div class="panel-heading">
<div class="panel-title">{{ dev.get_devtype_display|default:_('Title of the type of switch') }}.
{% if uptime %}
{% trans 'Uptime' %} {{ uptime }}
{% endif %}
</div>
</div>
<div class="panel-body">
<ul class="list-group">
<li class="list-group-item">{% trans 'Ip address' %}: {{ dev.ip_address|default:'-' }}</li>
<li class="list-group-item">{% trans 'Mac' %}: {{ dev.mac_addr }}</li>
<li class="list-group-item">{% trans 'Description' %}: {{ dev.comment }}</li>
{% for da in dev_accs %}
<li class="list-group-item">{% trans 'Attached user' %}:
{% if da.group %}
<a href="{% url 'abonapp:abon_home' da.group.pk da.username %}"
target="_blank">{{ da.get_full_name }}</a>
{% else %}
{{ da.get_full_name }}
{% endif %}
</li>
{% endfor %}
{% if dev.parent_dev %}
<li class="list-group-item">
{% with pdev=dev.parent_dev pdgrp=dev.parent_dev.group %}
{% trans 'Parent device' %}:
<a href="{% url 'devapp:view' pdgrp.pk pdev.pk %}"
title="{{ pdev.mac_addr|default:'' }}"
target="_blank">
{{ pdev.ip_address|default:'-' }} {{ pdev.comment }}
</a>
{% endwith %}
</li>
{% endif %}
</ul>
</div>
</div>
</div>
<div class="col-xs-12 col-sm-6">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{% trans 'ONU Status' %}</h3>
</div>
<div class="panel-body">
{% if onu_details %}
{% if onu_details.err %}
<div class="media">
<div class="media-left"><span class="media-object glyphicon glyphicon-remove-sign text-danger font-extra-large"></span></div>
</div>
<div class="media-body">
<b>{% trans 'ONU error' %}</b>: {{ onu_details.err }}<br>
</div>
{% else %}
<div class="media">
<div class="media-left font-extra-large">
{% if onu_details.status == '1' %}
<span class="media-object glyphicon glyphicon-ok-sign text-success"></span>
{% elif onu_details.status == '2' %}
<span class="media-object glyphicon glyphicon-remove-sign text-danger"></span>
{% else %}
<span class="media-object glyphicon glyphicon-question-sign"></span>
{% endif %}
</div>
<div class="media-body">
<b>{% trans 'Name on OLT' %}</b>: {{ onu_details.name }}<br>
<b>{% trans 'Distance(m)' %}</b>: {{ onu_details.distance }}<br>
<b>{% trans 'Signal' %}</b>: {{ onu_details.signal }}<br>
</div>
</div>
{% endif %}
{% else %}
<h3>{% trans 'Info does not fetch' %}</h3>
{% endif %}
</div>
</div>
</div>
</div>
{% endwith %}
{% endblock %}

4
devapp/templates/devapp/dev.html

@ -52,7 +52,7 @@
</div>
{% 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 %}
@ -61,7 +61,7 @@
<span class="glyphicon glyphicon-save"></span> {% trans 'Save' %}
</button>
{% if perms.devapp.delete_device %}
<a href="{% url 'devapp:del' group.pk|default:0 dev.pk %}" class="btn btn-danger">
<a href="{% url 'devapp:del' group.pk|default:0 dev.pk %}" class="btn btn-danger btn-modal">
<span class="glyphicon glyphicon-remove"></span> {% trans 'Delete' %}
</a>
{% else %}

14
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 %}
<h4>{% trans 'Are you sure you want to delete device?' %}</h4>
{% endblock %}

4
devapp/templates/devapp/devices.html

@ -30,7 +30,6 @@
{% if order_by == 'comment' %}<span class="glyphicon glyphicon-filter"></span>{% endif %}
</th>
<th class="col-md-3">{% trans 'Mac address' %}</th>
{# <th class="col-md-3 hidden-xs hidden-sm">{% trans 'Plugin output' %}</th> #}
<th class="col-md-1">
<a href="{% url 'devapp:devs' group.pk %}?{% url_replace request order_by='devtype' dir=dir|default:'down' %}">
{% trans 'Device type' %}
@ -61,11 +60,10 @@
<td><a href="{% url 'devapp:view' grpid dev.pk %}">{{ dev.ip_address }}</a></td>
<td>{{ dev.comment }}</td>
<td>{{ dev.mac_addr|default:_('Not assigned') }}</td>
{# <td class="hidden-xs hidden-sm">{{ dev.mon.plugin_output|default:'&ndash;' }}</td> #}
<td>{{ dev.get_devtype_display }}</td>
<td class="btn-group btn-group-xs btn-group-justified">
{% if can_del_dev %}
<a href="{% url 'devapp:del' grpid dev.pk %}" class="btn btn-danger">
<a href="{% url 'devapp:del' grpid dev.pk %}" class="btn btn-danger btn-modal">
<span class="glyphicon glyphicon-remove"></span>
</a>
{% endif %}

4
devapp/templates/devapp/devices_null_group.html

@ -46,12 +46,12 @@
<td>{{ dev.get_devtype_display }}</td>
<td class="btn-group btn-group-sm">
{% if can_del_dev %}
<a href="{% url 'devapp:del' 0 dev.pk %}" class="btn btn-danger btn-sm">
<a href="{% url 'devapp:del' 0 dev.pk %}" class="btn btn-danger btn-modal">
<span class="glyphicon glyphicon-remove"></span>
</a>
{% endif %}
{% if can_change_dev %}
<a href="{% url 'devapp:edit' 0 dev.pk %}" class="btn btn-default btn-sm">
<a href="{% url 'devapp:edit' 0 dev.pk %}" class="btn btn-default">
<span class="glyphicon glyphicon-edit"></span>
</a>
{% endif %}

2
devapp/urls.py

@ -10,7 +10,7 @@ urlpatterns = [
url(r'^(?P<group_id>\d+)$', views.DevicesListView.as_view(), name='devs'),
url(r'^(?P<group_id>\d+)/add$', views.dev, name='add'),
url(r'^(\d+)/(?P<device_id>\d+)$', views.devview, name='view'),
url(r'^(\d+)/(?P<device_id>\d+)/del$', views.devdel, name='del'),
url(r'^(\d+)/(?P<device_id>\d+)/del$', views.DeviceVeleteView.as_view(), name='del'),
url(r'^(?P<group_id>\d+)/(?P<device_id>\d+)/add$', views.add_single_port, name='add_port'),
url(r'^(?P<group_id>\d+)/(?P<device_id>\d+)/edit$', views.dev, name='edit'),
url(r'^(\d+)/(?P<device_id>\d+)/ports$', views.manage_ports, name='manage_ports'),

45
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 mydefs 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):
@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:
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'))
self.object.update_dhcp(remove=True)
except DeviceDBException as e:
return res_error(request, e)
messages.error(request, e)
messages.success(request, _('Device successfully deleted'))
return res
@login_required
@ -137,7 +142,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 +510,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 = '<span class="glyphicon glyphicon-ok"></span> <span class="hidden-xs">%s</span>' % _('Fixed')
break
@ -619,11 +624,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 +656,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 +664,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"
)

2
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

3
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 mydefs 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

70
mydefs.py → djing/lib/__init__.py

@ -1,16 +1,9 @@
# -*- coding: utf-8 -*-
from datetime import timedelta
from json import dumps
import socket
import struct
from abc import ABCMeta
from datetime import timedelta
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):
@ -41,20 +34,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"
@ -105,19 +84,7 @@ class MyChoicesAdapter(Iterator):
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
# Russian localized timedelta
class RuTimedelta(timedelta):
def __new__(cls, tm):
if isinstance(tm, timedelta):
@ -148,23 +115,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)):
@ -176,12 +126,10 @@ 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_]
class Singleton(type):
_instances = {}
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]

0
djing/auth_backends.py → djing/lib/auth_backends.py

35
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

0
messaging/__init__.py → djing/lib/messaging/__init__.py

7
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")

0
messaging/sms/base.py → djing/lib/messaging/sms/base.py

0
messaging/sms/consts.py → djing/lib/messaging/sms/consts.py

8
messaging/sms/deliver.py → 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,
from djing.lib.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.sms import consts
from djing.lib.messaging.sms.submit import SmsBase
from djing.lib.messaging.sms.udh import UserDataHeader
class SmsDeliver(SmsBase):

0
messaging/sms/gsm0338.py → djing/lib/messaging/sms/gsm0338.py

0
messaging/sms/pdu.py → djing/lib/messaging/sms/pdu.py

10
messaging/sms/submit.py → 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,
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 messaging.sms.base import SmsBase
from messaging.sms.gsm0338 import is_gsm_text
from messaging.sms.pdu import Pdu
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}$")

0
messaging/sms/udh.py → djing/lib/messaging/sms/udh.py

2
messaging/sms/wap.py → 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):

0
messaging/utils.py → djing/lib/messaging/utils.py

4
djing/lib/tln/__init__.py

@ -0,0 +1,4 @@
from .tln import *
__all__ = ('TelnetApi', 'ValidationError', 'ZTEFiberNumberNotFound', 'ZTEFiberIsFull',
'OnuZteRegisterError', 'ZteOltConsoleError', 'register_onu_ZTE_F660')

266
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)

2
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'
)

2
docs/dev.md

@ -343,7 +343,7 @@ def check_news(request):
return HttpResponse(dumps(r))
```
Убедитесь что вашему представлению не будет доступа от абонентов, об этом позаботится декоратор *only_admins* из *mydefs*. *mydefs* лежит в корне проекта.
Убедитесь что вашему представлению не будет доступа от абонентов, об этом позаботится декоратор *only_admins* из *djing.lib*.
После получения сообщения надо вернуть словарь с параметрами:

23
group_app/templates/group_app/group_confirm_delete.html

@ -1,13 +1,14 @@
{% extends 'base_delete_modal.html' %}
{% load i18n %}
<form role="form" action="{% url 'group_app:del' object.pk %}" method="post">{% csrf_token %}
<div class="modal-header warning">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title"><span class="glyphicon glyphicon-list-alt"></span>{% trans 'Remove group' %}</h4>
</div>
<div class="modal-body">
{% block modal_form_url %}
{% url 'group_app:del' object.pk %}
{% endblock %}
{% block modal_form_title %}
{% trans 'Remove group' %}
{% endblock %}
{% block modal_form_text %}
<h4>{% blocktrans %}Are you sure you want to delete group {{ object }}?{% endblocktrans %}</h4>
<button type="submit" class="btn btn-danger" value="DELETE">
<span class="glyphicon glyphicon-remove"></span> {% trans 'Delete' %}
</button>
</div>
</form>
{% endblock %}

2
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

7
messaging/sms/__init__.py

@ -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"]

359
migrate_to_0.2.py

@ -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')

2
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():

18
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;
}

5
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)),
],

2
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

4
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

3
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 "Вы уверены что хотите удалить тариф?"

2
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

30
tariff_app/templates/tariff_app/modal_del_warning.html

@ -1,30 +0,0 @@
{% load i18n %}
<form role="form" action="{% url 'tarifs:del' tid %}" method="post"> {% csrf_token %}
<div class="modal-header warning">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title"><span class="glyphicon glyphicon-exclamation-sign"></span>{% trans 'Delete service' %}</h4>
</div>
{% include 'message_block.html' %}
<div class="modal-body">
<div class="form-group-sm">
<label for="id_dev">{% trans 'Attention' %}</label>
<p>{% blocktrans %}after delete the tariff, subscribers who use that tariff will be disconnected from it.{% endblocktrans %}</p>
<input type="hidden" name="confirm" value="yes">
</div>
<div class="btn-group">
<button type="submit" class="btn btn-sm btn-danger">
<span class="glyphicon glyphicon-remove"></span> {% trans 'Delete' %}
</button>
<button type="reset" class="btn btn-sm btn-default">
<span class="glyphicon glyphicon-retweet"></span> {% trans 'Reset' %}
</button>
</div>
</div>
</form>

15
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 %}
<label>{% trans 'Are you sure you want to delete tariff?' %}</label>
<p>{% blocktrans %}after delete the tariff, subscribers who use that tariff will be disconnected from it.{% endblocktrans %}</p>
{% endblock %}

2
tariff_app/urls.py

@ -8,7 +8,7 @@ urlpatterns = [
url(r'^$', views.TariffsListView.as_view(), name='home'),
url(r'^(?P<tarif_id>\d+)$', views.edit_tarif, name='edit'),
url(r'^add$', views.edit_tarif, name='add'),
url(r'^del(?P<tid>\d+)$', views.del_tarif, name='del'),
url(r'^del(?P<tid>\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'),

36
tariff_app/views.py

@ -1,18 +1,18 @@
# -*- 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
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.decorators.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'):
@ -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()
@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'))
else:
messages.error(request, _('Not have a confirmations of delete'))
return mydefs.res_success(request, 'tarifs:home')
return render_to_text('tariff_app/modal_del_warning.html', {'tid': tid}, request=request)
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

2
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):

2
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',

8
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,15 @@ 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 safe_int, MultipleException, RuTimedelta
from djing.lib.decorators import only_admins
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 +250,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')

19
templates/base_delete_modal.html

@ -0,0 +1,19 @@
{% load i18n %}
<form role="form" action="{% block modal_form_url %}#modal_url{% endblock %}" method="post">{% csrf_token %}
<div class="modal-header warning">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">
<span class="glyphicon glyphicon-earphone"></span>
{% block modal_form_title %}Form title{% endblock %}
</h4>
</div>
<div class="modal-body">
{% block modal_form_text %}
<h4>{% trans 'Are you sure about them?' %}</h4>
{% endblock %}
<button type="submit" class="btn btn-danger" value="DELETE">
<span class="glyphicon glyphicon-remove"></span>
{% block modal_btn_delete_text %}{% trans 'Delete' %}{% endblock %}
</button>
</div>
</form>
Loading…
Cancel
Save