Browse Source

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

# Conflicts:
#	abonapp/locale/ru/LC_MESSAGES/django.po
#	abonapp/models.py
#	abonapp/templates/abonapp/peoples.html
#	abonapp/urls_abon.py
#	abonapp/views.py
#	bugs.txt
#	cron.py
#	djing/utils/save_from_nodeny.py
devel
bashmak 9 years ago
parent
commit
3b5d54e65f
  1. 3
      Doc.txt
  2. 17
      README.md
  3. 59
      abonapp/__init__.py
  4. 2
      abonapp/admin.py
  5. 25
      abonapp/forms.py
  6. 392
      abonapp/locale/ru/LC_MESSAGES/django.po
  7. 6
      abonapp/migrations/0014_auto_20170330_1452.py
  8. 42
      abonapp/migrations/0021_auto_20170705_1403.py
  9. 41
      abonapp/migrations/0022_auto_20170816_1109.py
  10. 64
      abonapp/models.py
  11. 8
      abonapp/pay_systems.py
  12. 40
      abonapp/templates/abonapp/activate_service.html
  13. 22
      abonapp/templates/abonapp/buy_tariff.html
  14. 75
      abonapp/templates/abonapp/editAbon.html
  15. 38
      abonapp/templates/abonapp/modal_addstreet.html
  16. 4
      abonapp/templates/abonapp/modal_dev.html
  17. 38
      abonapp/templates/abonapp/modal_editstreet.html
  18. 35
      abonapp/templates/abonapp/peoples.html
  19. 99
      abonapp/templates/abonapp/service.html
  20. 9
      abonapp/urls_abon.py
  21. 202
      abonapp/views.py
  22. 32
      accounts_app/locale/ru/LC_MESSAGES/django.po
  23. 21
      accounts_app/migrations/0007_auto_20170816_1109.py
  24. 2
      accounts_app/models.py
  25. 2
      accounts_app/templates/accounts/index.html
  26. 21
      accounts_app/views.py
  27. 44
      agent/commands/dhcp.py
  28. 31
      agent/core.py
  29. 106
      agent/mod_mikrotik.py
  30. 6
      agent/netflow/djing_flow.conf
  31. 35
      agent/structs.py
  32. 1
      bugs.txt
  33. 15
      chatbot/locale/ru/LC_MESSAGES/django.po
  34. 2
      chatbot/telebot.py
  35. 111
      clientsideapp/locale/ru/LC_MESSAGES/django.po
  36. 6
      clientsideapp/templates/clientsideapp/ext.html
  37. 3
      clientsideapp/templates/clientsideapp/index.html
  38. 28
      clientsideapp/templates/clientsideapp/modal_activate_service.html
  39. 32
      clientsideapp/templates/clientsideapp/modal_complete_service.html
  40. 10
      clientsideapp/templates/clientsideapp/modal_service_buy.html
  41. 26
      clientsideapp/templates/clientsideapp/modal_unsubscribe_service.html
  42. 94
      clientsideapp/templates/clientsideapp/services.html
  43. 3
      clientsideapp/urls.py
  44. 114
      clientsideapp/views.py
  45. 1
      devapp/admin.py
  46. 10
      devapp/base_intr.py
  47. 158
      devapp/dev_types.py
  48. 42
      devapp/forms.py
  49. 114
      devapp/locale/ru/LC_MESSAGES/django.po
  50. 2
      devapp/migrations/0005_auto_20170502_2232.py
  51. 48
      devapp/migrations/0006_auto_20170705_1403.py
  52. 20
      devapp/migrations/0007_auto_20170816_1109.py
  53. 74
      devapp/models.py
  54. 34
      devapp/onu_register.sh
  55. 22
      devapp/templates/devapp/add_dev.html
  56. 18
      devapp/templates/devapp/custom_dev_page/olt.html
  57. 42
      devapp/templates/devapp/custom_dev_page/onu.html
  58. 89
      devapp/templates/devapp/custom_dev_page/ports.html
  59. 36
      devapp/templates/devapp/dev.html
  60. 20
      devapp/templates/devapp/devices.html
  61. 4
      devapp/templates/devapp/devices_null_group.html
  62. 9
      devapp/templates/devapp/ext.htm
  63. 93
      devapp/templates/devapp/manage_ports/add_ports.html
  64. 60
      devapp/templates/devapp/manage_ports/list.html
  65. 37
      devapp/templates/devapp/manage_ports/modal_add_edit_port.html
  66. 18
      devapp/templates/devapp/manage_ports/modal_del_port.html
  67. 12
      devapp/urls.py
  68. 236
      devapp/views.py
  69. 55
      dialing_app/locale/ru/LC_MESSAGES/django.po
  70. 43
      dialing_app/migrations/0001_initial.py
  71. 10
      dialing_app/models.py
  72. 40
      dialing_app/templates/ext.html
  73. 26
      dialing_app/templates/index.html
  74. 57
      dialing_app/templates/vmail.html
  75. 2
      dialing_app/templatetags/telephone_filters.py
  76. 3
      dialing_app/urls.py
  77. 17
      dialing_app/views.py
  78. 38
      djing/__init__.py
  79. 0
      djing/fields.py
  80. 3
      djing/formfields.py
  81. 4
      djing/settings_example.py
  82. 2
      djing/urls.py
  83. 32
      djing/utils/load_dot_from_nodeny.py
  84. 92
      djing/utils/load_from_nodeny.py
  85. 45
      djing/utils/push_snmp_passw.py
  86. 29
      djing/utils/save_dot_from_nodeny.py
  87. 280
      djing/utils/save_from_nodeny.py
  88. 80
      docs/install.md
  89. 7
      docs/intro.md
  90. 2
      mapapp/templates/maps/map_tooltip.html
  91. 7
      photo_app/models.py
  92. 8
      queue_mngr.py
  93. 1
      requirements.txt
  94. 2
      searchapp/views.py
  95. 11
      static/css/custom.css
  96. 70
      static/js/my.js
  97. 18
      statistics/migrations/0002_delete_statelem.py
  98. 34
      statistics/migrations/0002_statcache.py
  99. 64
      statistics/models.py
  100. 15
      systemd_units/djing_queue.service

3
Doc.txt

@ -13,3 +13,6 @@
и ваш класс для своей логики расчёта тарифа и ваш класс для своей логики расчёта тарифа
ВАЖНО! Для отработки своевременного выключения услуги, время на сервере биллинга и NAS должно быть настроено точно. ВАЖНО! Для отработки своевременного выключения услуги, время на сервере биллинга и NAS должно быть настроено точно.
Таблицу кеша статистики лучше сделать в памяти т.к. будет часто обновляться
ALTER TABLE flowcache ENGINE=MEMORY;

17
README.md

@ -2,19 +2,6 @@
Попытка интернет биллинга. djing сокращение от django-billing. Это web интерфейс управления абонентами интернет-провайдера. Попытка интернет биллинга. djing сокращение от django-billing. Это web интерфейс управления абонентами интернет-провайдера.
Сейчас идёт тестирвоание работы на Mikrotik, функционал пока минимальный, т.к. пишу в свободное время. Работа планируется в реальных условиях и на реальных абонентах. Сейчас идёт тестирвоание работы на Mikrotik, функционал пока минимальный, т.к. пишу в свободное время. Работа планируется в реальных условиях и на реальных абонентах.
Использовано python 3, django 1.9, bootstrap 3, и другое в файле requirements.txt Использовано python 3, django 1.9, bootstrap 3, и другое в файле requirements.txt
Может:
Наблюдать за устройствами по snmp
Отправлять изменения мгновенно на mikrotik
Привязывать на карте к точкам топологии устройства
Есть привязка монтажника к группам абонентов
Есть менеджер задач на абонентов. Это оператор может выбрать абонента и описать проблему. Система отправит оповещение через telegram ответственному за групу указанного абонента монтажнику с текстом проблемы, адресом и телефоном абонента.
## Установка(не завершил описание):
На ArchLinux нужые пакеты я устанавливаю так:
```
# pacman -Sy mariadb-clients python3 python-pip nginx uwsgi
```
Дальше ставим всё для python через pip:
```
# pip install git+https://github.com/nerosketch/djing.git
```
## Содержание
* [Установка](./docs/install.md)

59
abonapp/__init__.py

@ -1,59 +0,0 @@
from django.conf import settings
from netaddr import mac_unix, mac_eui48
import importlib
import warnings
class mac_linux(mac_unix):
"""MAC format with zero-padded all upper-case hex and colon separated"""
word_fmt = '%.2X'
def default_dialect(eui_obj=None):
# Check to see if a default dialect class has been specified in settings,
# using 'module.dialect_cls' string and use importlib and getattr to retrieve dialect class. 'module' is the module and
# 'dialect_cls' is the class name of the custom dialect. The dialect must either be defined or imported by the module's
# __init__.py if the module is a package.
from .fields import MACAddressField # Remove import at v1.4
if hasattr(settings, 'MACADDRESS_DEFAULT_DIALECT') and not MACAddressField.dialect:
module, dialect_cls = settings.MACADDRESS_DEFAULT_DIALECT.split('.')
dialect = getattr(importlib.import_module(module), dialect_cls, mac_linux)
return dialect
else:
if MACAddressField.dialect: # Remove this "if" statement at v1.4
warnings.warn(
"The set_dialect class method on MACAddressField has been deprecated, in favor of the default_dialect "
"utility function and settings.MACADDRESS_DEFAULT_DIALECT. See macaddress.__init__.py source or the "
"project README for more information.",
DeprecationWarning,
)
return MACAddressField.dialect
if eui_obj:
return eui_obj.dialect
else:
return mac_linux
def format_mac(eui_obj, dialect):
# Format a EUI instance as a string using the supplied dialect class, allowing custom string classes by
# passing directly or as a string, a la 'module.dialect_cls', where 'module' is the module and 'dialect_cls'
# is the class name of the custom dialect. The dialect must either be defined or imported by the module's __init__.py if
# the module is a package.
if not isinstance(dialect, mac_eui48):
if isinstance(dialect, str):
module, dialect_cls = dialect.split('.')
dialect = getattr(importlib.import_module(module), dialect_cls)
eui_obj.dialect = dialect
return str(eui_obj)
from pkg_resources import get_distribution, DistributionNotFound
import os.path
try:
_dist = get_distribution('django-macaddress')
except DistributionNotFound:
__version__ = 'Please install this project with setup.py'
else:
__version__ = _dist.version
VERSION = __version__ # synonym
default_app_config = 'abonapp.apps.AbonappConfig'

2
abonapp/admin.py

@ -13,6 +13,4 @@ admin.site.register(models.AllTimePayLog)
admin.site.register(models.AbonRawPassword) admin.site.register(models.AbonRawPassword)
admin.site.register(models.ExtraFieldsModel) admin.site.register(models.ExtraFieldsModel)
admin.site.register(models.AllPayLog) admin.site.register(models.AllPayLog)
admin.site.register(models.Opt82)
admin.site.register(models.PassportInfo) admin.site.register(models.PassportInfo)
admin.site.register(models.AbonDevice)

25
abonapp/forms.py

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from django.http import QueryDict
from datetime import datetime from datetime import datetime
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django import forms from django import forms
@ -7,7 +6,6 @@ from django.contrib.auth.hashers import make_password
from random import choice from random import choice
from string import digits, ascii_lowercase from string import digits, ascii_lowercase
from . import models from . import models
from .formfields import MACAddressField
def generate_random_username(length=6, chars=digits, split=2, delimiter=''): def generate_random_username(length=6, chars=digits, split=2, delimiter=''):
@ -88,19 +86,6 @@ class AbonForm(forms.ModelForm):
return acc return acc
class Opt82Form(forms.ModelForm):
mac = MACAddressField(widget=forms.TextInput(attrs={'class': 'form-control', 'pattern': r'^([0-9A-Fa-f]{2}[:]){5}([0-9A-Fa-f]{2})$', 'required': ''}))
class Meta:
model = models.Opt82
fields = '__all__'
widgets = {
'port': forms.NumberInput(attrs={'class': 'form-control', 'required': ''})
}
#def save(self, commit=True):
# super().save(commit=commit)
class AbonGroupForm(forms.ModelForm): class AbonGroupForm(forms.ModelForm):
class Meta: class Meta:
model = models.AbonGroup model = models.AbonGroup
@ -136,3 +121,13 @@ class ExtraFieldForm(forms.ModelForm):
'field_type': forms.Select(attrs={'class': 'form-control'}), 'field_type': forms.Select(attrs={'class': 'form-control'}),
'data': forms.TextInput(attrs={'class': 'form-control'}) 'data': forms.TextInput(attrs={'class': 'form-control'})
} }
class AbonStreetForm(forms.ModelForm):
class Meta:
model = models.AbonStreet
fields = '__all__'
widgets = {
'name': forms.TextInput(attrs={'class': 'form-control', 'required':'', 'autofocus':''}),
'group': forms.Select(attrs={'class': 'form-control'})
}

392
abonapp/locale/ru/LC_MESSAGES/django.po

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-05-22 11:59+0300\n"
"POT-Creation-Date: 2017-06-15 13:35+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Dmitry Novikov nerosketch@gmail.com\n" "Last-Translator: Dmitry Novikov nerosketch@gmail.com\n"
"Language: ru\n" "Language: ru\n"
@ -20,28 +20,26 @@ msgstr ""
"%100>=11 && n%100<=14)? 2 : 3);\n" "%100>=11 && n%100<=14)? 2 : 3);\n"
#: abonapp/formfields.py:12 #: abonapp/formfields.py:12
#, fuzzy
#| msgid "Enter a valid MAC Address."
msgid "Enter a valid integer." msgid "Enter a valid integer."
msgstr "Введите валидный mac адрес"
msgstr "Введите верное число"
#: abonapp/formfields.py:21 #: abonapp/formfields.py:21
msgid "Enter a valid MAC Address." msgid "Enter a valid MAC Address."
msgstr "Введите валидный mac адрес" msgstr "Введите валидный mac адрес"
#: abonapp/forms.py:43 abonapp/templates/abonapp/addAbon.html.py:21
#: abonapp/forms.py:42 abonapp/templates/abonapp/addAbon.html.py:21
#: abonapp/templates/abonapp/editAbon.html:15 #: abonapp/templates/abonapp/editAbon.html:15
#: abonapp/templates/abonapp/viewAbon.html:28 #: abonapp/templates/abonapp/viewAbon.html:28
msgid "login" msgid "login"
msgstr "Логин" msgstr "Логин"
#: abonapp/forms.py:56 abonapp/templates/abonapp/editAbon.html.py:22
#: abonapp/templates/abonapp/peoples.html:36
#: abonapp/forms.py:55 abonapp/templates/abonapp/editAbon.html.py:22
#: abonapp/templates/abonapp/peoples.html:37
#: abonapp/templates/abonapp/viewAbon.html:32 #: abonapp/templates/abonapp/viewAbon.html:32
msgid "fio" msgid "fio"
msgstr "ФИО" msgstr "ФИО"
#: abonapp/forms.py:61
#: abonapp/forms.py:60
msgid "telephone placeholder" msgid "telephone placeholder"
msgstr "+[7,8,9,3] и 10,11 цифр" msgstr "+[7,8,9,3] и 10,11 цифр"
@ -69,7 +67,7 @@ msgstr "Текстовое поле"
msgid "Floating field" msgid "Floating field"
msgstr "Дробное с плавающей точкой" msgstr "Дробное с плавающей точкой"
#: abonapp/models.py:165 abonapp/templates/abonapp/editAbon.html.py:36
#: abonapp/models.py:165 abonapp/templates/abonapp/editAbon.html.py:43
#: abonapp/templates/abonapp/viewAbon.html:50 #: abonapp/templates/abonapp/viewAbon.html:50
msgid "Ip Address" msgid "Ip Address"
msgstr "IP Адрес" msgstr "IP Адрес"
@ -78,23 +76,26 @@ msgstr "IP Адрес"
msgid "Double invalid value" msgid "Double invalid value"
msgstr "Введите число с плавающей запятой" msgstr "Введите число с плавающей запятой"
#: abonapp/models.py:243
#: abonapp/models.py:244
msgid "Buy service perm" msgid "Buy service perm"
msgstr "Покупка тарифа абоненту" msgstr "Покупка тарифа абоненту"
#: abonapp/models.py:244
#: abonapp/models.py:245
msgid "Can view passport" msgid "Can view passport"
msgstr "Может просматривать паспортные данные" msgstr "Может просматривать паспортные данные"
#: abonapp/models.py:288
#: abonapp/models.py:292
msgid "Buy service default log" msgid "Buy service default log"
msgstr "Покупка тарифного плана через админку" msgstr "Покупка тарифного плана через админку"
#: abonapp/models.py:346
#: abonapp/models.py:307
msgid "service overdue log"
msgstr "Услуга просрочена, отключаем, и подключаем новую"
#: abonapp/models.py:347
msgid "Ip address already exist" msgid "Ip address already exist"
msgstr "Такой ip уже у кого-то есть" msgstr "Такой ip уже у кого-то есть"
#: abonapp/templates/abonapp/activate_service.html:8
#: abonapp/templates/abonapp/addAbon.html:7 #: abonapp/templates/abonapp/addAbon.html:7
#: abonapp/templates/abonapp/addGroup.html:7 #: abonapp/templates/abonapp/addGroup.html:7
#: abonapp/templates/abonapp/addInvoice.html:7 #: abonapp/templates/abonapp/addInvoice.html:7
@ -108,42 +109,22 @@ msgstr "Такой ip уже у кого-то есть"
#: abonapp/templates/abonapp/invoiceForPayment.html:7 #: abonapp/templates/abonapp/invoiceForPayment.html:7
#: abonapp/templates/abonapp/log.html:7 #: abonapp/templates/abonapp/log.html:7
#: abonapp/templates/abonapp/peoples.html:8 #: abonapp/templates/abonapp/peoples.html:8
#: abonapp/templates/abonapp/peoples.html:127
#: abonapp/templates/abonapp/peoples.html:132
msgid "User groups" msgid "User groups"
msgstr "Группы абонентов" msgstr "Группы абонентов"
#: abonapp/templates/abonapp/activate_service.html:11
#: abonapp/templates/abonapp/activate_service.html:18
#: abonapp/templates/abonapp/services.html:44 #: abonapp/templates/abonapp/services.html:44
msgid "Activate service" msgid "Activate service"
msgstr "Активировать услугу" msgstr "Активировать услугу"
#: abonapp/templates/abonapp/activate_service.html:26
#, python-format
msgid ""
"\n"
"Are you sure that you want activate service for the user?<br>\n"
"Note that the account will be removed from his money and open access to the "
"resources of the paid services.<br>\n"
"Maintenance cost is %(amount)s. On account of %(ballance)s, will be "
"%(diff)s<br>\n"
"It will work until %(deadline)s"
msgstr ""
"\n"
"Вы уверены что хотите активировать абоненту эту услугу?<br>Обратите внимание "
"что с его счёта <b>снимутся деньги</b> и откроется доступ к ресурсам "
"оплаченной услуги.<br>Стоимость услуги: %(amount)sруб. На счету %(ballance)s "
"руб, останется %(diff)s руб.<br>Услуга будет действовать до %(deadline)s"
#: abonapp/templates/abonapp/activate_service.html:34
#: abonapp/templates/abonapp/addAbon.html:91 #: abonapp/templates/abonapp/addAbon.html:91
#: abonapp/templates/abonapp/addGroup.html:29 #: abonapp/templates/abonapp/addGroup.html:29
#: abonapp/templates/abonapp/addInvoice.html:46 #: abonapp/templates/abonapp/addInvoice.html:46
#: abonapp/templates/abonapp/buy_tariff.html:57 #: abonapp/templates/abonapp/buy_tariff.html:57
#: abonapp/templates/abonapp/complete_service.html:49 #: abonapp/templates/abonapp/complete_service.html:49
#: abonapp/templates/abonapp/editAbon.html:96
#: abonapp/templates/abonapp/editAbon.html:165
#: abonapp/templates/abonapp/editAbon.html:211
#: abonapp/templates/abonapp/editAbon.html:103
#: abonapp/templates/abonapp/editAbon.html:192
#: abonapp/templates/abonapp/editAbon.html:238
#: abonapp/templates/abonapp/group_tariffs.html:29 #: abonapp/templates/abonapp/group_tariffs.html:29
#: abonapp/templates/abonapp/modal_dev.html:29 #: abonapp/templates/abonapp/modal_dev.html:29
#: abonapp/templates/abonapp/passport_view.html:49 #: abonapp/templates/abonapp/passport_view.html:49
@ -152,8 +133,8 @@ msgstr "Сохранить"
#: abonapp/templates/abonapp/addAbon.html:9 #: abonapp/templates/abonapp/addAbon.html:9
#: abonapp/templates/abonapp/addAbon.html:16 #: abonapp/templates/abonapp/addAbon.html:16
#: abonapp/templates/abonapp/peoples.html:110
#: abonapp/templates/abonapp/peoples.html:121
#: abonapp/templates/abonapp/peoples.html:115
#: abonapp/templates/abonapp/peoples.html:126
msgid "Add abon" msgid "Add abon"
msgstr "Добавить абонента" msgstr "Добавить абонента"
@ -163,13 +144,13 @@ msgstr "Фамилия и Имя"
#: abonapp/templates/abonapp/addAbon.html:37 #: abonapp/templates/abonapp/addAbon.html:37
#: abonapp/templates/abonapp/editAbon.html:29 #: abonapp/templates/abonapp/editAbon.html:29
#: abonapp/templates/abonapp/peoples.html:52
#: abonapp/templates/abonapp/peoples.html:53
#: abonapp/templates/abonapp/viewAbon.html:36 #: abonapp/templates/abonapp/viewAbon.html:36
msgid "Telephone" msgid "Telephone"
msgstr "Телефон" msgstr "Телефон"
#: abonapp/templates/abonapp/addAbon.html:45 #: abonapp/templates/abonapp/addAbon.html:45
#: abonapp/templates/abonapp/editAbon.html:66
#: abonapp/templates/abonapp/editAbon.html:73
#: abonapp/templates/abonapp/viewAbon.html:16 #: abonapp/templates/abonapp/viewAbon.html:16
msgid "User group" msgid "User group"
msgstr "Группа" msgstr "Группа"
@ -177,7 +158,7 @@ msgstr "Группа"
#: abonapp/templates/abonapp/addAbon.html:53 #: abonapp/templates/abonapp/addAbon.html:53
#: abonapp/templates/abonapp/addInvoice.html:40 #: abonapp/templates/abonapp/addInvoice.html:40
#: abonapp/templates/abonapp/debtors.html:22 #: abonapp/templates/abonapp/debtors.html:22
#: abonapp/templates/abonapp/editAbon.html:87
#: abonapp/templates/abonapp/editAbon.html:94
#: abonapp/templates/abonapp/invoiceForPayment.html:23 #: abonapp/templates/abonapp/invoiceForPayment.html:23
#: abonapp/templates/abonapp/log.html:20 #: abonapp/templates/abonapp/log.html:20
#: abonapp/templates/abonapp/payHistory.html:12 #: abonapp/templates/abonapp/payHistory.html:12
@ -186,19 +167,19 @@ msgid "Comment"
msgstr "Комментарий" msgstr "Комментарий"
#: abonapp/templates/abonapp/addAbon.html:59 #: abonapp/templates/abonapp/addAbon.html:59
#: abonapp/templates/abonapp/editAbon.html:43
#: abonapp/templates/abonapp/peoples.html:42
#: abonapp/templates/abonapp/editAbon.html:50
#: abonapp/templates/abonapp/peoples.html:43
#: abonapp/templates/abonapp/viewAbon.html:40 #: abonapp/templates/abonapp/viewAbon.html:40
msgid "Street" msgid "Street"
msgstr "Улица" msgstr "Улица"
#: abonapp/templates/abonapp/addAbon.html:67 #: abonapp/templates/abonapp/addAbon.html:67
#: abonapp/templates/abonapp/peoples.html:48
#: abonapp/templates/abonapp/peoples.html:49
msgid "Apartment" msgid "Apartment"
msgstr "Квартира" msgstr "Квартира"
#: abonapp/templates/abonapp/addAbon.html:76 #: abonapp/templates/abonapp/addAbon.html:76
#: abonapp/templates/abonapp/editAbon.html:73
#: abonapp/templates/abonapp/editAbon.html:80
#: abonapp/templates/abonapp/viewAbon.html:54 #: abonapp/templates/abonapp/viewAbon.html:54
msgid "Password" msgid "Password"
msgstr "Пароль" msgstr "Пароль"
@ -257,7 +238,7 @@ msgstr "Купить новую услугу (заказать тариф) дл
#: abonapp/templates/abonapp/debtors.html:20 #: abonapp/templates/abonapp/debtors.html:20
#: abonapp/templates/abonapp/log.html:19 #: abonapp/templates/abonapp/log.html:19
#: abonapp/templates/abonapp/payHistory.html:8 #: abonapp/templates/abonapp/payHistory.html:8
#: abonapp/templates/abonapp/peoples.html:23
#: abonapp/templates/abonapp/peoples.html:24
msgid "Sub" msgid "Sub"
msgstr "Абонент" msgstr "Абонент"
@ -266,6 +247,19 @@ msgstr "Абонент"
msgid "currency" msgid "currency"
msgstr "руб" msgstr "руб"
#: abonapp/templates/abonapp/charts.html:9
msgid "Graph of use"
msgstr "График использования"
#: abonapp/templates/abonapp/charts.html:44
#: abonapp/templates/abonapp/charts.html:56
msgid "Static info was Not found"
msgstr "Статистика не найдена"
#: abonapp/templates/abonapp/charts.html:51
msgid "Graphs by dates"
msgstr "Графики по датам"
#: abonapp/templates/abonapp/complete_service.html:10 #: abonapp/templates/abonapp/complete_service.html:10
#: abonapp/templates/abonapp/complete_service.html:17 #: abonapp/templates/abonapp/complete_service.html:17
#: abonapp/templates/abonapp/services.html:72 #: abonapp/templates/abonapp/services.html:72
@ -327,15 +321,54 @@ msgstr "Автор"
msgid "Debts not found" msgid "Debts not found"
msgstr "Нет должников" msgstr "Нет должников"
#: abonapp/templates/abonapp/dial_log.html:8
msgid "Play"
msgstr "Слушать"
#: abonapp/templates/abonapp/dial_log.html:9
msgid "calldate"
msgstr "дата звонка"
#: abonapp/templates/abonapp/dial_log.html:10
msgid "src"
msgstr "кто"
#: abonapp/templates/abonapp/dial_log.html:11
msgid "dst"
msgstr "куда"
#: abonapp/templates/abonapp/dial_log.html:12
msgid "duration"
msgstr "продолжительность"
#: abonapp/templates/abonapp/dial_log.html:13
msgid "start"
msgstr "начало"
#: abonapp/templates/abonapp/dial_log.html:14
msgid "answer"
msgstr "ответ"
#: abonapp/templates/abonapp/dial_log.html:15
msgid "end"
msgstr "конец"
#: abonapp/templates/abonapp/dial_log.html:16
msgid "disposition"
msgstr "состояние"
#: abonapp/templates/abonapp/dial_log.html:38
msgid "Calls was not found"
msgstr "Звонки не найдены"
#: abonapp/templates/abonapp/editAbon.html:9 #: abonapp/templates/abonapp/editAbon.html:9
msgid "Change subscriber" msgid "Change subscriber"
msgstr "Изменение абонента" msgstr "Изменение абонента"
#: abonapp/templates/abonapp/editAbon.html:38
#: abonapp/templates/abonapp/editAbon.html:189
#: abonapp/templates/abonapp/peoples.html:81
#: abonapp/templates/abonapp/peoples.html:83
#: abonapp/templates/abonapp/peoples.html:84
#: abonapp/templates/abonapp/editAbon.html:45
#: abonapp/templates/abonapp/editAbon.html:216
#: abonapp/templates/abonapp/peoples.html:86
#: abonapp/templates/abonapp/peoples.html:88
#: abonapp/templates/abonapp/viewAbon.html:18 #: abonapp/templates/abonapp/viewAbon.html:18
#: abonapp/templates/abonapp/viewAbon.html:29 #: abonapp/templates/abonapp/viewAbon.html:29
#: abonapp/templates/abonapp/viewAbon.html:33 #: abonapp/templates/abonapp/viewAbon.html:33
@ -346,55 +379,54 @@ msgstr "Изменение абонента"
msgid "Not assigned" msgid "Not assigned"
msgstr "&lt;Не назначен&gt;" msgstr "&lt;Не назначен&gt;"
#: abonapp/templates/abonapp/editAbon.html:51
#: abonapp/templates/abonapp/peoples.html:48
#: abonapp/templates/abonapp/editAbon.html:58
#: abonapp/templates/abonapp/viewAbon.html:46 #: abonapp/templates/abonapp/viewAbon.html:46
msgid "House" msgid "House"
msgstr "Дом" msgstr "Дом"
#: abonapp/templates/abonapp/editAbon.html:59
#: abonapp/templates/abonapp/editAbon.html:66
#: abonapp/templates/abonapp/viewAbon.html:22 #: abonapp/templates/abonapp/viewAbon.html:22
msgid "Is active" msgid "Is active"
msgstr "Активен" msgstr "Активен"
#: abonapp/templates/abonapp/editAbon.html:99
#: abonapp/templates/abonapp/editAbon.html:106
msgid "Send account info to user" msgid "Send account info to user"
msgstr "Отправить данные абоненту" msgstr "Отправить данные абоненту"
#: abonapp/templates/abonapp/editAbon.html:102
#: abonapp/templates/abonapp/editAbon.html:103
#: abonapp/templates/abonapp/editAbon.html:109
#: abonapp/templates/abonapp/editAbon.html:110
#, fuzzy #, fuzzy
#| msgid "Add debt" #| msgid "Add debt"
msgid "Add new task" msgid "Add new task"
msgstr "Добавить квитанцию" msgstr "Добавить квитанцию"
#: abonapp/templates/abonapp/editAbon.html:116
#: abonapp/templates/abonapp/editAbon.html:123
msgid "Remove clutch" msgid "Remove clutch"
msgstr "Удалить муфту" msgstr "Удалить муфту"
#: abonapp/templates/abonapp/editAbon.html:120
#: abonapp/templates/abonapp/editAbon.html:127
#: abonapp/templates/abonapp/modal_dev.html:6 #: abonapp/templates/abonapp/modal_dev.html:6
msgid "Add clutch" msgid "Add clutch"
msgstr "Добавить муфту" msgstr "Добавить муфту"
#: abonapp/templates/abonapp/editAbon.html:143
#: abonapp/templates/abonapp/editAbon.html:170
msgid "DHCP information" msgid "DHCP information"
msgstr "DHCP информация" msgstr "DHCP информация"
#: abonapp/templates/abonapp/editAbon.html:149
#: abonapp/templates/abonapp/editAbon.html:176
msgid "Mac Address" msgid "Mac Address"
msgstr "Мак" msgstr "Мак"
#: abonapp/templates/abonapp/editAbon.html:156
#: abonapp/templates/abonapp/editAbon.html:183
msgid "Port" msgid "Port"
msgstr "Порт" msgstr "Порт"
#: abonapp/templates/abonapp/editAbon.html:167
#: abonapp/templates/abonapp/editAbon.html:194
msgid "Reset option82" msgid "Reset option82"
msgstr "Сбросить option82" msgstr "Сбросить option82"
#: abonapp/templates/abonapp/editAbon.html:168
#: abonapp/templates/abonapp/editAbon.html:192
#: abonapp/templates/abonapp/editAbon.html:195
#: abonapp/templates/abonapp/editAbon.html:219
msgid "Delete" msgid "Delete"
msgstr "Удалить" msgstr "Удалить"
@ -501,7 +533,7 @@ msgid "Refill"
msgstr "Пополнить" msgstr "Пополнить"
#: abonapp/templates/abonapp/modal_dev.html:13 #: abonapp/templates/abonapp/modal_dev.html:13
msgid "Select a device"
msgid "Select the device"
msgstr "Выберите устройство" msgstr "Выберите устройство"
#: abonapp/templates/abonapp/modal_extra_field.html:11 #: abonapp/templates/abonapp/modal_extra_field.html:11
@ -551,44 +583,76 @@ msgstr "Пополнить счёт"
msgid "The people in the selected group" msgid "The people in the selected group"
msgstr "Народ в выбранной группе" msgstr "Народ в выбранной группе"
#: abonapp/templates/abonapp/peoples.html:27
#: abonapp/templates/abonapp/peoples.html:28
msgid "Last traffic" msgid "Last traffic"
msgstr "Последний траффик"
msgstr "Траф."
#: abonapp/templates/abonapp/peoples.html:30
#: abonapp/templates/abonapp/peoples.html:31
#, fuzzy #, fuzzy
#| msgid "Ip Address" #| msgid "Ip Address"
msgid "Ip address" msgid "Ip address"
msgstr "IP Адрес" msgstr "IP Адрес"
#: abonapp/templates/abonapp/peoples.html:53
#: abonapp/templates/abonapp/peoples.html:54
#: abonapp/templates/abonapp/services.html:10 #: abonapp/templates/abonapp/services.html:10
msgid "Service" msgid "Service"
msgstr "Услуга" msgstr "Услуга"
#: abonapp/templates/abonapp/peoples.html:56
#: abonapp/templates/abonapp/peoples.html:57
msgid "Ballance" msgid "Ballance"
msgstr "Балланс" msgstr "Балланс"
#: abonapp/templates/abonapp/peoples.html:108
#: abonapp/templates/abonapp/peoples.html:113
msgid "Subscribers not found" msgid "Subscribers not found"
msgstr "Абоненты не найдены" msgstr "Абоненты не найдены"
#: abonapp/templates/abonapp/peoples.html:125
#: abonapp/templates/abonapp/peoples.html:130
msgid "Refresh subscribers on NAS" msgid "Refresh subscribers on NAS"
msgstr "Обновить абонентов в NAS" msgstr "Обновить абонентов в NAS"
#: abonapp/templates/abonapp/peoples.html:128
#: abonapp/templates/abonapp/peoples.html:133
msgid "Tariffs in groups" msgid "Tariffs in groups"
msgstr "Тарифы в группах" msgstr "Тарифы в группах"
#: abonapp/templates/abonapp/peoples.html:141
#: abonapp/templates/abonapp/peoples.html:148
msgid "No streets found for that group" msgid "No streets found for that group"
msgstr "Не найдены улицы для группы" msgstr "Не найдены улицы для группы"
#: abonapp/templates/abonapp/services.html:5 #: abonapp/templates/abonapp/services.html:5
msgid "Services of subscriber"
msgstr "Купленные абонентом услуги (назначенные тарифные планы)"
msgid "Subscriber's service"
msgstr "Текущая услуга абонента"
msgid "Add street"
msgstr "Добавить улицу"
msgid "Edit streets"
msgstr "Редактировать улицы"
msgid "Street successfully saved"
msgstr "Улица успешно сохранена"
msgid "Streets has been saved"
msgstr "Улицы сохранены"
msgid "Street title"
msgstr "Название улицы"
msgid "One of these streets has not been found"
msgstr "Одна из этих улиц не была найдена"
msgid "The street has not been found"
msgstr "Улица не найдена"
msgid "The street successfully deleted"
msgstr "Улица успешно удалена"
msgid "Streets has not been found"
msgstr "Улицы не найдены"
#: abonapp/templates/abonapp/services.html:9
msgid "Priority"
msgstr "Приоритет"
#: abonapp/templates/abonapp/services.html:12 #: abonapp/templates/abonapp/services.html:12
msgid "Input speed" msgid "Input speed"
@ -646,241 +710,241 @@ msgstr "Просмотр абонента"
msgid "yes,no" msgid "yes,no"
msgstr "Да,Нет" msgstr "Да,Нет"
#: abonapp/views.py:74
#: abonapp/views.py:75
msgid "create group success msg" msgid "create group success msg"
msgstr "Группа успешно создана" msgstr "Группа успешно создана"
#: abonapp/views.py:77 abonapp/views.py:137 abonapp/views.py:282
#: abonapp/views.py:337 abonapp/views.py:426 abonapp/views.py:637
#: abonapp/views.py:779
#: abonapp/views.py:78 abonapp/views.py:138 abonapp/views.py:284
#: abonapp/views.py:342 abonapp/views.py:432 abonapp/views.py:648
#: abonapp/views.py:791
msgid "fix form errors" msgid "fix form errors"
msgstr "Некоторые поля заполнены не правильно, проверте ещё раз" msgstr "Некоторые поля заполнены не правильно, проверте ещё раз"
#: abonapp/views.py:113 abonapp/views.py:176
#: abonapp/views.py:114 abonapp/views.py:177
msgid "delete group success msg" msgid "delete group success msg"
msgstr "Группа успешно удалена" msgstr "Группа успешно удалена"
#: abonapp/views.py:134
#: abonapp/views.py:135
msgid "create abon success msg" msgid "create abon success msg"
msgstr "Абонент успешно создан" msgstr "Абонент успешно создан"
#: abonapp/views.py:148
#: abonapp/views.py:149
msgid "Address" msgid "Address"
msgstr "Адрес" msgstr "Адрес"
#: abonapp/views.py:170
#: abonapp/views.py:171
msgid "delete abon success msg" msgid "delete abon success msg"
msgstr "Абонент успешно удалён" msgstr "Абонент успешно удалён"
#: abonapp/views.py:179
#: abonapp/views.py:180
msgid "I not know what to delete" msgid "I not know what to delete"
msgstr "Не понятно что удалять" msgstr "Не понятно что удалять"
#: abonapp/views.py:183
#: abonapp/views.py:184
#, python-format #, python-format
msgid "NAS says: '%s'" msgid "NAS says: '%s'"
msgstr "NAS сказал: '%s'" msgstr "NAS сказал: '%s'"
#: abonapp/views.py:199
#: abonapp/views.py:201
msgid "fill account through admin side" msgid "fill account through admin side"
msgstr "Пополнение счёта через админку" msgstr "Пополнение счёта через админку"
#: abonapp/views.py:201
#: abonapp/views.py:203
#, python-format #, python-format
msgid "Account filled successfully on %.2f" msgid "Account filled successfully on %.2f"
msgstr "" msgstr ""
#: abonapp/views.py:204
#: abonapp/views.py:206
msgid "I not know the account id" msgid "I not know the account id"
msgstr "Счёт успешно пополнен на %.2f" msgstr "Счёт успешно пополнен на %.2f"
#: abonapp/views.py:280
#: abonapp/views.py:282
msgid "edit abon success msg" msgid "edit abon success msg"
msgstr "Абонент успешно изменён" msgstr "Абонент успешно изменён"
#: abonapp/views.py:295
#: abonapp/views.py:297
msgid "User has not have password, and cannot login" msgid "User has not have password, and cannot login"
msgstr "Для абонента не задан пароль, он не сможет войти в учётку" msgstr "Для абонента не задан пароль, он не сможет войти в учётку"
#: abonapp/views.py:297 abonapp/views.py:692
#: abonapp/views.py:299 abonapp/views.py:703
msgid "User device was not found" msgid "User device was not found"
msgstr "Пользовательское устройство не найдено" msgstr "Пользовательское устройство не найдено"
#: abonapp/views.py:350
#: abonapp/views.py:355
#, fuzzy #, fuzzy
#| msgid "Abon does not exist" #| msgid "Abon does not exist"
msgid "User does not exist" msgid "User does not exist"
msgstr "Абонент не найден" msgstr "Абонент не найден"
#: abonapp/views.py:383
#: abonapp/views.py:388
msgid "Receipt has been created" msgid "Receipt has been created"
msgstr "Квитанция на оплату была создана" msgstr "Квитанция на оплату была создана"
#: abonapp/views.py:413
#: abonapp/views.py:419
msgid "Tariff has been picked" msgid "Tariff has been picked"
msgstr "Тариф успешно выбран" msgstr "Тариф успешно выбран"
#: abonapp/views.py:421
#: abonapp/views.py:427
msgid "Tariff your picked does not exist" msgid "Tariff your picked does not exist"
msgstr "Тариф, который вы выбрали, не существует" msgstr "Тариф, который вы выбрали, не существует"
#: abonapp/views.py:484
#: abonapp/views.py:491
msgid "Refunds for unused resources" msgid "Refunds for unused resources"
msgstr "Возврат средств за неиспользованные ресурсы" msgstr "Возврат средств за неиспользованные ресурсы"
#: abonapp/views.py:490
#: abonapp/views.py:497
msgid "Service has been finished successfully" msgid "Service has been finished successfully"
msgstr "Услуга успешно завершена" msgstr "Услуга успешно завершена"
#: abonapp/views.py:493 abonapp/views.py:525
#: abonapp/views.py:500 abonapp/views.py:533
msgid "Not confirmed" msgid "Not confirmed"
msgstr "Действие не подтверждено" msgstr "Действие не подтверждено"
#: abonapp/views.py:528
#: abonapp/views.py:536
msgid "Service has been activated successfully" msgid "Service has been activated successfully"
msgstr "Услуга успешно активирована" msgstr "Услуга успешно активирована"
#: abonapp/views.py:554
#: abonapp/views.py:562
msgid "User has been detached from service" msgid "User has been detached from service"
msgstr "Абонент отвязан от услуги" msgstr "Абонент отвязан от услуги"
#: abonapp/views.py:634
#: abonapp/views.py:645
msgid "Passport information has been saved" msgid "Passport information has been saved"
msgstr "Информация о паспорте сохранена" msgstr "Информация о паспорте сохранена"
#: abonapp/views.py:642 abonapp/views.py:689 abonapp/views.py:709
#: abonapp/views.py:750
#: abonapp/views.py:653 abonapp/views.py:700 abonapp/views.py:720
#: abonapp/views.py:761
msgid "Abon does not exist" msgid "Abon does not exist"
msgstr "Абонент не найден" msgstr "Абонент не найден"
#: abonapp/views.py:645
#: abonapp/views.py:656
msgid "Passport info for the user does not exist" msgid "Passport info for the user does not exist"
msgstr "Для абонента не найдены паспортные данные" msgstr "Для абонента не найдены паспортные данные"
#: abonapp/views.py:682
#: abonapp/views.py:693
msgid "Device has successfully attached" msgid "Device has successfully attached"
msgstr "Устройство успешно прикреплено" msgstr "Устройство успешно прикреплено"
#: abonapp/views.py:687
#: abonapp/views.py:698
msgid "Device your selected already does not exist" msgid "Device your selected already does not exist"
msgstr "Устройство, выбранное вами, уже не существует" msgstr "Устройство, выбранное вами, уже не существует"
#: abonapp/views.py:707
#: abonapp/views.py:718
msgid "Device has successfully unattached" msgid "Device has successfully unattached"
msgstr "Устройство успешно откреплено" msgstr "Устройство успешно откреплено"
#: abonapp/views.py:753
#: abonapp/views.py:764
msgid "Group what you want doesn't exist" msgid "Group what you want doesn't exist"
msgstr "Указанная вами группа не найдена" msgstr "Указанная вами группа не найдена"
#: abonapp/views.py:777
#: abonapp/views.py:789
msgid "Extra field successfully created" msgid "Extra field successfully created"
msgstr "Динамичесое поле добавлено успешно" msgstr "Динамичесое поле добавлено успешно"
#: abonapp/views.py:808
#: abonapp/views.py:819
msgid "Extra fields has been saved" msgid "Extra fields has been saved"
msgstr "Динамические поля сохранены" msgstr "Динамические поля сохранены"
#: abonapp/views.py:810
#: abonapp/views.py:821
msgid "One or more extra fields has not been saved" msgid "One or more extra fields has not been saved"
msgstr "Поле или одно из полей не найдено" msgstr "Поле или одно из полей не найдено"
#: abonapp/views.py:822
#: abonapp/views.py:833
msgid "Extra field successfully deleted" msgid "Extra field successfully deleted"
msgstr "Динамическое поле успешно удалено" msgstr "Динамическое поле успешно удалено"
#: abonapp/views.py:832
#: abonapp/views.py:843
msgid "no ping" msgid "no ping"
msgstr "не пингуется" msgstr "не пингуется"
#: abonapp/views.py:839 abonapp/views.py:849
#: abonapp/views.py:850 abonapp/views.py:860
msgid "ping ok" msgid "ping ok"
msgstr "пингуется" msgstr "пингуется"
#: abonapp/views.py:844
#: abonapp/views.py:855
#, python-format #, python-format
msgid "ok ping, %d/%d loses" msgid "ok ping, %d/%d loses"
msgstr "пингуется, %d/%d" msgstr "пингуется, %d/%d"
#: abonapp/views.py:847
#: abonapp/views.py:858
#, python-format #, python-format
msgid "no ping, %d/%d loses" msgid "no ping, %d/%d loses"
msgstr "не пингуется, %d/%d" msgstr "не пингуется, %d/%d"
#: abonapp/templates/abonapp/ext.html:31
msgid "Services" msgid "Services"
msgstr "Услуги" msgstr "Услуги"
msgid "Payment history"
msgstr "История платежей"
#: abonapp/templates/abonapp/ext.html:43
msgid "Payments" msgid "Payments"
msgstr "Финансы" msgstr "Финансы"
#: abonapp/templates/abonapp/ext.html:48
msgid "History of tasks" msgid "History of tasks"
msgstr "История задач" msgstr "История задач"
msgid "Dynamic Field"
msgstr "Динамическое поле"
msgid "Instance of a option82 unexpectiadly disappeared"
msgstr "Экземпляр option82 неожиданно исчез из базы"
msgid "SNMP error on device"
msgstr "Ошибка в SNMP на устройстве"
#: abonapp/templates/abonapp/ext.html:53
msgid "Charts" msgid "Charts"
msgstr "Графики" msgstr "Графики"
#: abonapp/templates/abonapp/ext.html:26
msgid "Sub information" msgid "Sub information"
msgstr "Информация абонента" msgstr "Информация абонента"
msgid "Streets" msgid "Streets"
msgstr "Улицы" msgstr "Улицы"
msgid "Static info was Not found"
msgstr "Статистика не найдена"
msgid "Graphs by dates"
msgstr "Графики по датам"
msgid "Graph of use"
msgstr "График использования"
msgid "Play"
msgstr "Слушать"
msgid "calldate"
msgstr "дата звонка"
msgid "Dialing"
msgstr "Звонки"
msgid "src"
msgstr "кто"
msgid "Device port"
msgstr "Порт&nbsp;устройства"
msgid "dst"
msgstr "куда"
msgid "duration"
msgstr "продолжительность"
msgid "Ports does not exist"
msgstr "Порты не найдены"
msgid "start"
msgstr "начало"
msgid "User port has been saved"
msgstr "Порт абонента успешно выбран"
msgid "answer"
msgstr "ответ"
msgid "Selected port does not exist"
msgstr "Выбранный порт не существует"
msgid "end"
msgstr "конец"
msgid "Device"
msgstr "Устройство"
msgid "disposition"
msgstr "состояние"
msgid "Is dynamic network settings"
msgstr "Динамические настройки по dhcp"
msgid "Calls was not found"
msgstr "Звонки не найдены"
msgid "Method is not POST"
msgstr "Метод не POST"
msgid "Dialing"
msgstr "Звонки"
msgid "Call to"
msgstr "Позвонить"
msgid "That service already activated" msgid "That service already activated"
msgstr "Эта услуга уже подключена" msgstr "Эта услуга уже подключена"
msgid "Service already activated" msgid "Service already activated"
msgstr "Услуга уже подключена" msgstr "Услуга уже подключена"
msgid "We have a problem in DB: AbonTariff instance has no related to service"
msgstr "У нас проблема с БД: экземпляр AbonTariff не имеет отношения к тарифу"
msgid "Date of start"
msgstr "Дата начала"
msgid "Subscriber has no service"
msgstr "У абонента нет услуги"
msgid "This group has no services"
msgstr "У этой группы нет услуг"
msgid "Attach serices to groups"
msgstr "Привязать услуги к группам"
msgid "Attach services to group"
msgstr "Привязать услуги к этой группе"
msgid "User that is no staff can not buy admin services"
msgstr "Пользователь, который не является персоналом не может покупать услуги для внутренних нужд"

6
abonapp/migrations/0014_auto_20170330_1452.py

@ -2,9 +2,9 @@
# Generated by Django 1.9 on 2017-03-30 11:52 # Generated by Django 1.9 on 2017-03-30 11:52
from __future__ import unicode_literals from __future__ import unicode_literals
import abonapp.fields
from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
import djing.fields
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
@ -18,7 +18,7 @@ class Migration(migrations.Migration):
name='Opt82', name='Opt82',
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('mac', abonapp.fields.MACAddressField(integer=True)),
('mac', djing.fields.MACAddressField(integer=True)),
('port', models.PositiveSmallIntegerField(default=0)), ('port', models.PositiveSmallIntegerField(default=0)),
], ],
options={ options={

42
abonapp/migrations/0021_auto_20170705_1403.py

@ -0,0 +1,42 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2017-07-05 14:03
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('devapp', '0006_auto_20170705_1403'),
('abonapp', '0020_auto_20170517_1655'),
]
operations = [
migrations.RemoveField(
model_name='abon',
name='opt82',
),
migrations.DeleteModel(
name='Opt82',
),
migrations.AddField(
model_name='abon',
name='dev_port',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='devapp.Port'),
),
migrations.AddField(
model_name='abon',
name='device',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='devapp.Device'),
),
migrations.AddField(
model_name='abon',
name='is_dynamic_ip',
field=models.BooleanField(default=False),
),
migrations.DeleteModel(
name='AbonDevice',
),
]

41
abonapp/migrations/0022_auto_20170816_1109.py

@ -0,0 +1,41 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2017-08-16 11:09
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('abonapp', '0021_auto_20170705_1403'),
]
operations = [
migrations.AlterModelOptions(
name='abontariff',
options={'permissions': (('can_complete_service', 'Снятие со счёта средств'),)},
),
migrations.RemoveField(
model_name='abon',
name='current_tariffs',
),
migrations.AddField(
model_name='abon',
name='current_tariff',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='abonapp.AbonTariff'),
),
migrations.AlterUniqueTogether(
name='abontariff',
unique_together=set([]),
),
migrations.RemoveField(
model_name='abontariff',
name='abon',
),
migrations.RemoveField(
model_name='abontariff',
name='tariff_priority',
),
]

64
abonapp/models.py

@ -7,8 +7,8 @@ from django.utils.translation import ugettext as _
from agent import Transmitter, AbonStruct, TariffStruct, NasFailedResult, NasNetworkError from agent import Transmitter, AbonStruct, TariffStruct, NasFailedResult, NasNetworkError
from tariff_app.models import Tariff from tariff_app.models import Tariff
from accounts_app.models import UserProfile from accounts_app.models import UserProfile
from .fields import MACAddressField
from mydefs import MyGenericIPAddressField, ip2int, LogicError, ip_addr_regex from mydefs import MyGenericIPAddressField, ip2int, LogicError, ip_addr_regex
from djing import settings
class AbonGroup(models.Model): class AbonGroup(models.Model):
@ -41,16 +41,6 @@ class AbonLog(models.Model):
class AbonTariff(models.Model): class AbonTariff(models.Model):
def __init__(self, deadline=None, *args, **kwargs):
super(AbonTariff, self).__init__(*args, **kwargs)
calc_obj = self.tariff.get_calc_type()(self)
self.time_start = timezone.now()
if deadline is None:
self.deadline = calc_obj.calc_deadline()
else:
self.deadline = deadline
tariff = models.ForeignKey(Tariff, related_name='linkto_tariff') tariff = models.ForeignKey(Tariff, related_name='linkto_tariff')
# время начала действия услуги # время начала действия услуги
@ -68,9 +58,9 @@ class AbonTariff(models.Model):
return False if self.time_start is None else True return False if self.time_start is None else True
def __str__(self): def __str__(self):
return "'%s' - '%s'" % (
self.tariff.title,
self.abon.get_short_name()
return "%d: %s" % (
self.pk or 0,
self.tariff.title
) )
class Meta: class Meta:
@ -133,18 +123,6 @@ class ExtraFieldsModel(models.Model):
db_table = 'abon_extra_fields' db_table = 'abon_extra_fields'
class Opt82(models.Model):
mac = MACAddressField()
port = models.PositiveSmallIntegerField(default=0)
def __str__(self):
return "%s-%d" % (self.mac, self.port)
class Meta:
db_table = 'opt_82'
unique_together = (('mac', 'port'),)
class Abon(UserProfile): class Abon(UserProfile):
current_tariff = models.ForeignKey(AbonTariff, null=True, blank=True, on_delete=models.SET_NULL) current_tariff = models.ForeignKey(AbonTariff, null=True, blank=True, on_delete=models.SET_NULL)
group = models.ForeignKey(AbonGroup, models.SET_NULL, blank=True, null=True) group = models.ForeignKey(AbonGroup, models.SET_NULL, blank=True, null=True)
@ -154,7 +132,9 @@ class Abon(UserProfile):
street = models.ForeignKey(AbonStreet, on_delete=models.SET_NULL, null=True, blank=True) street = models.ForeignKey(AbonStreet, on_delete=models.SET_NULL, null=True, blank=True)
house = models.CharField(max_length=12, null=True, blank=True) house = models.CharField(max_length=12, null=True, blank=True)
extra_fields = models.ManyToManyField(ExtraFieldsModel, blank=True) extra_fields = models.ManyToManyField(ExtraFieldsModel, blank=True)
opt82 = models.ForeignKey(Opt82, null=True, blank=True, on_delete=models.SET_NULL)
device = models.ForeignKey('devapp.Device', null=True, blank=True, on_delete=models.SET_NULL)
dev_port = models.ForeignKey('devapp.Port', null=True, blank=True, on_delete=models.SET_NULL)
is_dynamic_ip = models.BooleanField(default=False)
# возвращает связь с текущим тарифом для абонента # возвращает связь с текущим тарифом для абонента
def active_tariff(self): def active_tariff(self):
@ -188,6 +168,9 @@ class Abon(UserProfile):
amount = round(tariff.amount, 2) amount = round(tariff.amount, 2)
if not author.is_staff and tariff.is_admin:
raise LogicError(_('User that is no staff can not buy admin services'))
if self.current_tariff is not None: if self.current_tariff is not None:
if self.current_tariff.tariff == tariff: if self.current_tariff.tariff == tariff:
# Эта услуга уже подключена # Эта услуга уже подключена
@ -256,18 +239,6 @@ class Abon(UserProfile):
super(Abon, self).save(*args, **kwargs) super(Abon, self).save(*args, **kwargs)
class AbonDevice(models.Model):
abon = models.ForeignKey(Abon)
device = models.ForeignKey('devapp.Device')
def __str__(self):
return "%s - %s" % (self.abon, self.device)
class Meta:
db_table = 'abon_device'
unique_together = ('abon', 'device')
class PassportInfo(models.Model): class PassportInfo(models.Model):
series = models.CharField(max_length=4, validators=[validators.integer_validator]) series = models.CharField(max_length=4, validators=[validators.integer_validator])
number = models.CharField(max_length=6, validators=[validators.integer_validator]) number = models.CharField(max_length=6, validators=[validators.integer_validator])
@ -346,7 +317,7 @@ class AbonRawPassword(models.Model):
def abon_post_save(sender, instance, **kwargs): def abon_post_save(sender, instance, **kwargs):
timeout = None timeout = None
if hasattr(instance, 'is_dhcp') and instance.is_dhcp: if hasattr(instance, 'is_dhcp') and instance.is_dhcp:
timeout = 14400
timeout = getattr(settings, 'DHCP_TIMEOUT', 14400)
agent_abon = instance.build_agent_struct() agent_abon = instance.build_agent_struct()
if agent_abon is None: if agent_abon is None:
return True return True
@ -377,5 +348,14 @@ def abon_del_signal(sender, instance, **kwargs):
return True return True
#models.signals.post_save.connect(abon_post_save, sender=Abon)
#models.signals.post_delete.connect(abon_del_signal, sender=Abon)
def abon_tariff_post_init(sender, instance, **kwargs):
if getattr(instance, 'time_start') is None:
instance.time_start = timezone.now()
calc_obj = instance.tariff.get_calc_type()(instance)
if getattr(instance, 'deadline') is None:
instance.deadline = calc_obj.calc_deadline()
models.signals.post_save.connect(abon_post_save, sender=Abon)
models.signals.post_delete.connect(abon_del_signal, sender=Abon)
models.signals.post_init.connect(abon_tariff_post_init, sender=AbonTariff)

8
abonapp/pay_systems.py

@ -62,13 +62,15 @@ def allpay(request):
pays = AllTimePayLog.objects.filter(pay_id=pay_id) pays = AllTimePayLog.objects.filter(pay_id=pay_id)
if pays.count() > 0: if pays.count() > 0:
return bad_ret(-100) return bad_ret(-100)
# тут в author передаём учётку абонента, т.к. это он сам через терминал пополняет
abon.add_ballance(abon, pay_amount, comment='AllPay %.2f' % pay_amount)
abon.save(update_fields=['ballance'])
AllTimePayLog.objects.create( AllTimePayLog.objects.create(
pay_id=pay_id, pay_id=pay_id,
summ=pay_amount summ=pay_amount
) )
# тут в author передаём учётку абонента, т.к. это он сам через терминал пополняет
abon.add_ballance(abon, pay_amount, comment='AllPay %.2f' % pay_amount)
abon.save(update_fields=['ballance'])
current_date = timezone.now().strftime("%d.%m.%Y %H:%M:%S") current_date = timezone.now().strftime("%d.%m.%Y %H:%M:%S")
return "<?xml version='1.0' encoding='UTF-8'?>" \ return "<?xml version='1.0' encoding='UTF-8'?>" \
"<pay-response>\n" +\ "<pay-response>\n" +\

40
abonapp/templates/abonapp/activate_service.html

@ -1,40 +0,0 @@
{% extends request.is_ajax|yesno:'bajax.html,base.html' %}
{% load i18n %}
{% block main %}
<ol class="breadcrumb">
<li><span class="glyphicon glyphicon-home"></span></li>
<li><a href="{% url 'abonapp:group_list' %}">{% trans 'User groups' %}</a></li>
<li><a href="{% url 'abonapp:people_list' abon_group.id %}">{{ abon_group.title }}</a></li>
<li><a href="{% url 'abonapp:abon_home' abon_group.id abon.id %}">{{ abon.fio }}</a></li>
<li class="active">{% trans 'Activate service' %}</li>
</ol>
{% include 'message_block.html' %}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{% trans 'Activate service' %}</h3>
</div>
<div class="panel-body">
<form role="form" action="{% url 'abonapp:activate_service' abon_group.id abon.id abtar.id %}"
method="post">{% csrf_token %}
<input name="finish_confirm" value="yes" type="hidden">
<p>
{% blocktrans with ballance=abon.ballance deadline=deadline|date:'d F Y, H:i:s' %}
Are you sure that you want activate service for the user?<br>
Note that the account will be removed from his money and open access to the resources of the paid services.<br>
Maintenance cost is {{ amount }}. On account of {{ ballance }}, will be {{ diff }}<br>
It will work until {{ deadline }}{% endblocktrans %}
</p>
<button type="submit" class="btn btn-sm btn-primary">
<span class="glyphicon glyphicon-save"></span> {% trans 'Save' %}
</button>
</form>
</div>
</div>
{% endblock %}

22
abonapp/templates/abonapp/buy_tariff.html

@ -22,13 +22,17 @@
<form role="form" action="{% url 'abonapp:pick_tariff' abon_group.pk abon.pk %}" <form role="form" action="{% url 'abonapp:pick_tariff' abon_group.pk abon.pk %}"
method="post">{% csrf_token %} method="post">{% csrf_token %}
<div class="form-group"> <div class="form-group">
{% if tariffs %}
<label for="id_tariffs">{% trans 'Pick a service' %}</label> <label for="id_tariffs">{% trans 'Pick a service' %}</label>
<div class="input-group"> <div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-bullhorn"></span></span> <span class="input-group-addon"><span class="glyphicon glyphicon-bullhorn"></span></span>
<select class="form-control" name="tariff" id="id_tariffs"> <select class="form-control" name="tariff" id="id_tariffs">
{% for trf in tariffs %} {% for trf in tariffs %}
<option value="{{ trf.pk }}" data-deadline="{{ trf.calc_deadline|date:"Y-m-d" }}">
{% if trf.pk == selected_tariff %}
<option value="{{ trf.pk }}" data-deadline='{{ trf.calc_deadline|date:"Y-m-d" }}' selected>
{% else %}
<option value="{{ trf.pk }}" data-deadline='{{ trf.calc_deadline|date:"Y-m-d" }}'>
{% endif %}
{{ trf.title }}. {{ trf.amount }}{% trans 'currency' %} (Вх:{{ trf.speedIn }}MBit/s. Исх:{{ trf.speedOut }} MBit/s) {{ trf.title }}. {{ trf.amount }}{% trans 'currency' %} (Вх:{{ trf.speedIn }}MBit/s. Исх:{{ trf.speedOut }} MBit/s)
</option> </option>
{% endfor %} {% endfor %}
@ -51,12 +55,22 @@
</script> </script>
</div> </div>
{% endif %} {% endif %}
{% else %}
<div class="alert alert-danger" role="alert">
<span class="glyphicon glyphicon-exclamation-sign"></span>
{% trans 'This group has no services' %},
<a href="{% url 'abonapp:ch_group_tariff' abon_group.pk %}">
{% trans 'Attach serices to groups' %}
</a>
</div>
{% endif %}
</div> </div>
<div class="btn-group"> <div class="btn-group">
<button type="submit" class="btn btn-sm btn-primary">
<button type="submit" class="btn btn-sm btn-primary"{% if not tariffs %} disabled{% endif %}>
<span class="glyphicon glyphicon-save"></span> {% trans 'Save' %} <span class="glyphicon glyphicon-save"></span> {% trans 'Save' %}
</button> </button>
<button type="reset" class="btn btn-sm btn-default">
<button type="reset" class="btn btn-sm btn-default"{% if not tariffs %} disabled{% endif %}>
<span class="glyphicon glyphicon-remove-circle"></span> {% trans 'Reset' %} <span class="glyphicon glyphicon-remove-circle"></span> {% trans 'Reset' %}
</button> </button>
</div> </div>

75
abonapp/templates/abonapp/editAbon.html

@ -31,7 +31,7 @@
<div class="input-group input-group-sm"> <div class="input-group input-group-sm">
{{ form.telephone }}{{ form.telephone.errors }} {{ form.telephone }}{{ form.telephone.errors }}
<span class="input-group-btn"> <span class="input-group-btn">
<a href="sip:{{ form.telephone.value }}" class="btn btn-default">
<a href="sip:{{ form.telephone.value }}" class="btn btn-default" title="{% trans 'Call to' %}">
<span class="glyphicon glyphicon-earphone"></span> <span class="glyphicon glyphicon-earphone"></span>
</a> </a>
</span> </span>
@ -42,7 +42,7 @@
<div class="form-group-sm{% if is_bad_ip %} has-error{% endif %}"> <div class="form-group-sm{% if is_bad_ip %} has-error{% endif %}">
<label for="id_ip" class="col-sm-4 control-label">{% trans 'Ip Address' %}</label> <label for="id_ip" class="col-sm-4 control-label">{% trans 'Ip Address' %}</label>
<div class="col-sm-8"> <div class="col-sm-8">
<input type="text" value="{{ ip|default:'' }}" class="form-control" name="ip" placeholder="{% trans 'Not assigned' %}" pattern="^(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]?)$"{% if abon.opt82 %} disabled{% endif %}/>
<input type="text" value="{{ ip|default:'' }}" class="form-control" name="ip" placeholder="{% trans 'Not assigned' %}" pattern="^(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]?)$"{% if abon.is_dynamic_ip %} disabled{% endif %}/>
</div> </div>
</div> </div>
@ -113,23 +113,6 @@
</div> </div>
</div> </div>
<div class="form-group-sm">
<div class="col-sm-offset-4 col-sm-8 btn-group btn-group-sm">
{% if device %}
<a href="{% url 'devapp:view' abon_group.pk device.pk %}" target="_blank" class="btn btn-sm btn-default" title="{{ device.ip_address }}">
<span class="glyphicon glyphicon-hdd"></span> {{ device.comment|truncatechars:11 }} {{ device.ip_address }}
</a>
<a href="{% url 'abonapp:clear_dev' abon_group.pk abon.pk %}" class="btn btn-sm btn-danger">
<span class="glyphicon glyphicon-remove-circle"></span> {% trans 'Remove clutch' %}
</a>
{% else %}
<a href="{% url 'abonapp:dev' abon_group.pk abon.pk %}" class="btn btn-success btn-sm btn-modal">
<span class="glyphicon glyphicon-plus"></span> {% trans 'Add clutch' %}
</a>
{% endif %}
</div>
</div>
{% if ip %} {% if ip %}
<div class="form-group-sm"> <div class="form-group-sm">
<div class="col-sm-offset-4 col-sm-8 btn-group btn-group-sm"> <div class="col-sm-offset-4 col-sm-8 btn-group btn-group-sm">
@ -147,36 +130,66 @@
<div class="col-sm-6"> <div class="col-sm-6">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<h3 class="panel-title">{% trans 'DHCP information' %}</h3>
<h3 class="panel-title">{% trans 'Select the device' %}</h3>
</div> </div>
<div class="panel-body"> <div class="panel-body">
<form role="form" class="form-horizontal" action="{% url 'abonapp:opt82' abon_group.pk abon.pk %}" method="post">{% csrf_token %}
<form role="form" class="form-horizontal" action="{% url 'abonapp:save_user_dev_port' abon_group.pk abon.pk %}" method="post">{% csrf_token %}
<div class="form-group-sm"> <div class="form-group-sm">
<label for="id_ip" class="col-sm-2 control-label">{% trans 'Mac Address' %}</label>
<div class="col-sm-10">
{{ tech_form.mac }}{{ tech_form.mac.errors }}
<label for="id_method" class="col-sm-2 control-label">{% trans 'Device' %}</label>
<div class="col-sm-10 btn-group btn-group-sm">
{% if device %}
<a href="{% url 'devapp:view' abon_group.pk device.pk %}" target="_blank" class="btn btn-sm btn-default" title="{% trans 'Mac Address' %} {{ device.mac_addr }}">
<span class="glyphicon glyphicon-hdd"></span> {{ device.comment|truncatechars:11 }} {{ device.ip_address }}
</a>
<a href="{% url 'abonapp:clear_dev' abon_group.pk abon.pk %}" class="btn btn-sm btn-danger">
<span class="glyphicon glyphicon-remove-circle"></span> {% trans 'Remove clutch' %}
</a>
{% else %}
<a href="{% url 'abonapp:dev' abon_group.pk abon.pk %}" class="btn btn-success btn-sm btn-modal">
<span class="glyphicon glyphicon-plus"></span> {% trans 'Add clutch' %}
</a>
{% endif %}
</div> </div>
</div> </div>
{% if device %}
<div class="form-group-sm"> <div class="form-group-sm">
<label for="id_port" class="col-sm-2 control-label">{% trans 'Port' %}</label>
<label for="id_dev_port" class="col-sm-2 control-label">{% trans 'Device port' %}</label>
<div class="col-sm-10"> <div class="col-sm-10">
{{ tech_form.port }}{{ tech_form.port.errors }}
<select id="id_dev_port" class="form-control" name="user_port">
<option value="0">{% trans 'Not assigned' %}</option>
{% for port in dev_ports %}
{% if port == abon.dev_port %}
<option value="{{ port.pk }}" selected>{{ port.num }}: {{ port.descr }}</option>
{% else %}
<option value="{{ port.pk }}">{{ port.num }}: {{ port.descr }}</option>
{% endif %}
{% empty %}
<option value="0">{% trans 'Ports does not exist' %}</option>
{% endfor %}
</select>
</div>
</div>
<div class="form-group-sm">
<div class="col-sm-offset-2 col-sm-10 checkbox">
<label>
<input type="checkbox" name="is_dynamic_ip"{% if abon.is_dynamic_ip %} checked{% endif %}> {% trans 'Is dynamic network settings' %}
</label>
</div> </div>
</div> </div>
<div class="form-group-sm"> <div class="form-group-sm">
<div class="col-sm-offset-2 col-sm-10 btn-group btn-group-sm"> <div class="col-sm-offset-2 col-sm-10 btn-group btn-group-sm">
<button type="submit" class="btn btn-primary btn-sm"> <button type="submit" class="btn btn-primary btn-sm">
<span class="glyphicon glyphicon-floppy-disk"></span> {% trans 'Save' %} <span class="glyphicon glyphicon-floppy-disk"></span> {% trans 'Save' %}
</button> </button>
<a href="{% url 'abonapp:opt82' abon_group.pk abon.pk %}?act=release" class="btn btn-danger btn-sm" title="{% trans 'Reset option82' %}">
<span class="glyphicon glyphicon-trash"></span> {% trans 'Delete' %}
</a>
<button class="btn btn-default" type="reset" title="Reset">
<span class="glyphicon glyphicon-repeat"></span>
</button>
</div> </div>
</div> </div>
{% endif %}
</form> </form>
</div> </div>
</div> </div>

38
abonapp/templates/abonapp/modal_addstreet.html

@ -0,0 +1,38 @@
{% load i18n %}
<form role="form" action="{% url 'abonapp:street_add' gid %}" method="post"> {% csrf_token %}
<div class="modal-header primary">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title"><span class="glyphicon glyphicon-plus"></span>{% trans 'Add street' %}</h4>
</div>
{% include 'message_block.html' %}
<div class="modal-body">
<div class="form-group-sm">
<label for="id_name">{% trans 'Street title' %}</label>
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-font"></span></span>
{{ form.name }}{{ form.name.errors }}
</div>
</div>
<div class="form-group-sm">
<label for="id_group">{% trans 'User group' %}</label>
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-user"></span></span>
{{ form.group }}{{ form.group.errors }}
</div>
</div>
<div class="btn-group">
<button type="submit" class="btn btn-sm btn-success">
<span class="glyphicon glyphicon-plus"></span> {% trans 'Add' %}
</button>
<button type="reset" class="btn btn-sm btn-default">
<span class="glyphicon glyphicon-remove-circle"></span> {% trans 'Reset' %}
</button>
</div>
</div>
</form>

4
abonapp/templates/abonapp/modal_dev.html

@ -10,12 +10,14 @@
<div class="modal-body"> <div class="modal-body">
<div class="form-group-sm"> <div class="form-group-sm">
<label for="id_dev">{% trans 'Select a device' %}</label>
<label for="id_dev">{% trans 'Select the device' %}</label>
<div class="input-group"> <div class="input-group">
<select name="dev" id="id_dev" class="form-control"> <select name="dev" id="id_dev" class="form-control">
{% for device in devices %} {% for device in devices %}
{% if device.has_attachable_to_subscriber %}
<option value="{{ device.pk }}">{{ device }}</option> <option value="{{ device.pk }}">{{ device }}</option>
{% endif %}
{% empty %} {% empty %}
<option value="0">---</option> <option value="0">---</option>
{% endfor %} {% endfor %}

38
abonapp/templates/abonapp/modal_editstreet.html

@ -0,0 +1,38 @@
{% load i18n %}
<form role="form" action="{% url 'abonapp:street_edit' gid %}" method="post"> {% csrf_token %}
<div class="modal-header primary">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title"><span class="glyphicon glyphicon-edit"></span>{% trans 'Edit streets' %}</h4>
</div>
{% include 'message_block.html' %}
<div class="modal-body">
{% for street in streets %}
<div class="form-group">
<div class="input-group input-group-sm">
<input type="text" class="form-control" value="{{ street.name }}" name="sname">
<input type="hidden" name="sid" value="{{ street.pk }}">
<span class="input-group-btn">
<a href="{% url 'abonapp:street_del' gid street.pk %}" class="btn btn-danger">
<span class="glyphicon glyphicon-remove"></span>
</a>
</span>
</div>
</div>
{% empty %}
<h4>{% trans 'Streets has not been found' %}</h4>
{% endfor %}
<div class="btn-group">
<button type="submit" class="btn btn-sm btn-primary">
<span class="glyphicon glyphicon-save"></span> {% trans 'Save' %}
</button>
<button type="reset" class="btn btn-sm btn-default">
<span class="glyphicon glyphicon-remove-circle"></span> {% trans 'Reset' %}
</button>
</div>
</div>
</form>

35
abonapp/templates/abonapp/peoples.html

@ -18,6 +18,7 @@
<table class="table table-striped table-bordered"> <table class="table table-striped table-bordered">
<thead> <thead>
<tr> <tr>
<th>#</th>
<th> <th>
<a href="{% url 'abonapp:people_list' abon_group.pk %}?order_by=username&dir={{ dir|default:"down" }}"> <a href="{% url 'abonapp:people_list' abon_group.pk %}?order_by=username&dir={{ dir|default:"down" }}">
{% trans 'Sub' %} {% trans 'Sub' %}
@ -45,7 +46,7 @@
</th> </th>
<th width="100"> <th width="100">
<a href="{% url 'abonapp:people_list' abon_group.pk %}?order_by=house&dir={{ dir|default:"down" }}"> <a href="{% url 'abonapp:people_list' abon_group.pk %}?order_by=house&dir={{ dir|default:"down" }}">
{% trans 'House' %}/{% trans 'Apartment' %}
{% trans 'Apartment' %}
</a> </a>
{% if order_by == 'house' %}<span class="glyphicon glyphicon-filter"></span>{% endif %} {% if order_by == 'house' %}<span class="glyphicon glyphicon-filter"></span>{% endif %}
</th> </th>
@ -57,31 +58,38 @@
</a> </a>
{% if order_by == 'ballance' %}<span class="glyphicon glyphicon-filter"></span>{% endif %} {% if order_by == 'ballance' %}<span class="glyphicon glyphicon-filter"></span>{% endif %}
</th> </th>
<th width="100">#</th>
<th width="10">#</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% with can_ch_trf=perms.tariff_app.change_tariff can_del_trf=perms.abonapp.delete_abon %}
{% for human in peoples %} {% for human in peoples %}
{% if human.is_active %} {% if human.is_active %}
<tr> <tr>
{% else %} {% else %}
<tr class="danger"> <tr class="danger">
{% endif %} {% endif %}
<td>{% if human.stat_cache and human.stat_cache.is_online %}
<span class="glyphicon glyphicon-ok text-success"></span>
{% else %}
<span class="glyphicon glyphicon-remove-sign text-muted"></span>
{% endif %}</td>
<td> <td>
<a href="{% url 'abonapp:abon_home' human.group.pk human.pk %}">{{ human.username }}</a> <a href="{% url 'abonapp:abon_home' human.group.pk human.pk %}">{{ human.username }}</a>
{% if human.is_online %}
<span class="glyphicon glyphicon-ok text-success"></span>
{% endif %}
</td> </td>
<td> <td>
{% if human.traf %}
{{ human.traf.cur_time|date:"D H:i" }}
{% if human.stat_cache %}
{% if human.stat_cache.is_today %}
{{ human.stat_cache.last_time|date:"H:i" }}
{% else %}
{{ human.stat_cache.last_time|date:"D H:i" }}
{% endif %}
{% endif %} {% endif %}
</td> </td>
<td>{{ human.ip_address|default:_('Not assigned') }}</td> <td>{{ human.ip_address|default:_('Not assigned') }}</td>
<td>{{ human.fio }}</td> <td>{{ human.fio }}</td>
<td>{{ human.street|default:_('Not assigned') }}</td> <td>{{ human.street|default:_('Not assigned') }}</td>
<td>{{ human.house|default:_('Not assigned') }}</td>
<td>{{ human.house|default:'-' }}</td>
<td><a href="tel:{{ human.telephone }}">{{ human.telephone }}</a></td> <td><a href="tel:{{ human.telephone }}">{{ human.telephone }}</a></td>
<td> <td>
{% if human.active_tariff %} {% if human.active_tariff %}
@ -95,7 +103,7 @@
</td> </td>
<td>{{ human.ballance }}</td> <td>{{ human.ballance }}</td>
<td> <td>
{% if perms.abonapp.delete_abon %}
{% if can_del_trf %}
<a href="{% url 'abonapp:del_abon' %}?t=a&id={{ human.pk }}" class="btn btn-danger btn-sm"> <a href="{% url 'abonapp:del_abon' %}?t=a&id={{ human.pk }}" class="btn btn-danger btn-sm">
<span class="glyphicon glyphicon-remove"></span> <span class="glyphicon glyphicon-remove"></span>
</a> </a>
@ -112,6 +120,7 @@
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
{% endwith %}
</tbody> </tbody>
<tfoot> <tfoot>
<tr> <tr>
@ -142,6 +151,14 @@
{% empty %} {% empty %}
<a href="#" class="list-group-item">{% trans 'No streets found for that group' %}</a> <a href="#" class="list-group-item">{% trans 'No streets found for that group' %}</a>
{% endfor %} {% endfor %}
<div class="btn-group btn-group-sm btn-group-justified">
<a href="{% url 'abonapp:street_add' abon_group.pk %}" class="btn btn-success btn-modal" data-toggle="tooltip" title="{% trans 'Add street' %}">
<span class="glyphicon glyphicon-plus"></span> {% trans 'Add' %}
</a>
<a href="{% url 'abonapp:street_edit' abon_group.pk %}" class="btn btn-primary btn-modal" data-toggle="tooltip" title="{% trans 'Edit streets' %}">
<span class="glyphicon glyphicon-edit"></span> {% trans 'Edit' %}
</a>
</div>
</div> </div>
</div> </div>
</div> </div>

99
abonapp/templates/abonapp/service.html

@ -7,31 +7,58 @@
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<h3 class="panel-title">{% trans 'Services of subscriber' %}</h3>
<h3 class="panel-title">{% trans "Subscriber's service" %}</h3>
</div> </div>
<div class="panel-body"> <div class="panel-body">
{% if abon_tariff %} {% if abon_tariff %}
<p>
<b>{% trans 'Service' %}</b>:
<dl class="dl-horizontal">
<dt>{% trans 'Service' %}</dt>
<dd>
{% if abon_tariff.tariff %}
{% if perms.tariff_app.change_tariff %} {% if perms.tariff_app.change_tariff %}
<a href="{% url 'tarifs:edit' abon_tariff.pk %}" title="{{ abon_tariff.time_start|default:'' }}">
<a href="{% url 'tarifs:edit' abon_tariff.tariff.pk %}" title="{{ abon_tariff.time_start|default:'' }}">
{{ abon_tariff.tariff.title }} {{ abon_tariff.tariff.title }}
</a> </a>
{% else %} {% else %}
{{ abon_tariff.tariff.title }} {{ abon_tariff.tariff.title }}
{% endif %} {% endif %}
</p>
<i><b>{% trans 'Sum' %}</b>: {{ abon_tariff.tarif.amount }} руб.</i>
<p><b>{% trans 'Input speed' %}</b>: {{ abon_tariff.tariff.speedIn }}</p>
<p><b>{% trans 'Output speed' %}</b>: {{ abon_tariff.tariff.speedOut }}</p>
<p><b>{% trans 'Works until' %}</b>: {{ abon_tariff.deadline|date:"d E Y, l" }}</p>
<p>{{ abon_tariff.tarif.descr }}</p>
{% else %} {% else %}
No service, <a href="{% url 'abonapp:pick_tariff' abon_group.pk abon.pk %}" class="btn btn-sm btn-success">
<span class="glyphicon glyphicon-plus"></span> {% trans 'Buy service' %}
<b class="text-danger">{% trans 'We have a problem in DB: AbonTariff instance has no related to service' %}</b>
{% endif %}
</dd>
<dt>{% trans 'Sum' %}</dt>
<dd>{{ abon_tariff.tariff.amount }} {% trans 'currency' %}.</dd>
<dt>{% trans 'Input speed' %}</dt>
<dd>{{ abon_tariff.tariff.speedIn }}</dd>
<dt>{% trans 'Output speed' %}</dt>
<dd>{{ abon_tariff.tariff.speedOut }}</dd>
<dt>{% trans 'Date of start' %}</dt>
<dd>{{ abon_tariff.time_start|date:"d E Y, l H:i" }}</dd>
<dt>{% trans 'Works until' %}</dt>
<dd>{{ abon_tariff.deadline|date:"d E Y, l H:i" }}</dd>
</dl>
<blockquote>
<p>{{ abon_tariff.tariff.descr }}</p>
</blockquote>
{% else %}
{% trans 'Subscriber has no service' %}.
<a href="{% url 'abonapp:pick_tariff' abon_group.pk abon.pk %}">
{% trans 'Buy service' %}
</a> </a>
{% endif %} {% endif %}
{% if abon_tariff %}
<a href="{% url 'abonapp:unsubscribe_service' abon_group.pk abon.pk abon_tariff.pk %}" class="btn btn-danger">
<span class="glyphicon glyphicon-remove-circle"></span> {% trans 'Finish service' %}
</a>
{% endif %}
</div> </div>
</div> </div>
</div> </div>
@ -41,14 +68,52 @@
<h3 class="panel-title">Услуги для заказа</h3> <h3 class="panel-title">Услуги для заказа</h3>
</div> </div>
<div class="panel-body"> <div class="panel-body">
<table class="table table-condensed">
<thead>
<tr>
<th>{% trans 'Pick a service' %}</th>
<th>{% trans 'Service' %}</th>
<th>{% trans 'Price' %}</th>
<th>{% trans 'Speed In' %}</th>
<th>{% trans 'Speed Out' %}</th>
</tr>
</thead>
<tbody>
{% with can_ch_trf=perms.tariff_app.change_tariff %}
{% for service in services %}
<tr>
<td><a href="{% url 'abonapp:pick_tariff' abon_group.pk abon.pk %}?selected_tariff={{ service.pk }}"
class="btn btn-sm btn-default" title="{{ service.get_calc_type_display }}" data-toggle="tooltip"{% if abon_tariff %} disabled{% endif %}>
<span class="glyphicon glyphicon-shopping-cart"></span>
</a></td>
<td>
{% if can_ch_trf %}
<a href="{% url 'tarifs:edit' service.pk %}" title="{{ service.descr }}" data-toggle="tooltip"><b>{{ service.title }}</b></a>
{% else %}
{{ service.title }}
{% endif %}
</td>
<td>{{ service.amount }} {% trans 'currency' %}</td>
<td>{{ service.speedIn }}</td>
<td>{{ service.speedOut }}</td>
</tr>
{% empty %}
<tr><td colspan="5">
{% trans 'This group has no services' %}
<a href="{% url 'abonapp:ch_group_tariff' abon_group.pk %}" class="btn btn-sm btn-default" title="{% trans 'User groups' %}">
<span class="glyphicon glyphicon-cog"></span> {% trans 'Tariffs in groups' %}
</a>
</td></tr>
{% endfor %}
{% endwith %}
</tbody>
</table>
<a href="{% url 'abonapp:ch_group_tariff' abon_group.pk %}" class="btn btn-sm btn-default" title="{% trans 'User groups' %}">
<span class="glyphicon glyphicon-cog"></span> {% trans 'Attach services to group' %}
</a>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div>
{% endblock %} {% endblock %}

9
abonapp/urls_abon.py

@ -6,6 +6,9 @@ urlpatterns = [
url(r'^$', views.peoples, name='people_list'), url(r'^$', views.peoples, name='people_list'),
url(r'^addabon$', views.addabon, name='add_abon'), url(r'^addabon$', views.addabon, name='add_abon'),
url(r'^services$', views.chgroup_tariff, name='ch_group_tariff'), url(r'^services$', views.chgroup_tariff, name='ch_group_tariff'),
url(r'^street/add$', views.street_add, name='street_add'),
url(r'^street/edit', views.street_edit, name='street_edit'),
url(r'^street/(?P<sid>\d+)/delete$', views.street_del, name='street_del'),
url(r'^(?P<uid>\d+)$', views.abonhome, name='abon_home'), url(r'^(?P<uid>\d+)$', views.abonhome, name='abon_home'),
url(r'^(?P<uid>\d+)/services$', views.abon_services, name='abon_services'), url(r'^(?P<uid>\d+)/services$', views.abon_services, name='abon_services'),
@ -17,18 +20,18 @@ urlpatterns = [
url(r'^(?P<uid>\d+)/pick$', views.pick_tariff, name='pick_tariff'), url(r'^(?P<uid>\d+)/pick$', views.pick_tariff, name='pick_tariff'),
url(r'^(?P<uid>\d+)/passport_view$', views.passport_view, name='passport_view'), url(r'^(?P<uid>\d+)/passport_view$', views.passport_view, name='passport_view'),
url(r'^(?P<uid>\d+)/complete_service(?P<srvid>\d+)$', views.complete_service, name='compl_srv'), url(r'^(?P<uid>\d+)/complete_service(?P<srvid>\d+)$', views.complete_service, name='compl_srv'),
url(r'^(?P<uid>\d+)/opt82$', views.opt82, name='opt82'),
url(r'^(?P<uid>\d+)/chart$', views.charts, name='charts'), url(r'^(?P<uid>\d+)/chart$', views.charts, name='charts'),
url(r'^(?P<uid>\d+)/dials$', views.dials, name='dials'), url(r'^(?P<uid>\d+)/dials$', views.dials, name='dials'),
url(r'^(?P<uid>\d+)/extra_field$', views.make_extra_field, name='extra_field'), url(r'^(?P<uid>\d+)/extra_field$', views.make_extra_field, name='extra_field'),
url(r'^(?P<uid>\d+)/extra_field/(?P<fid>\d+)/delete$', views.extra_field_delete, name='extra_field_delete'), url(r'^(?P<uid>\d+)/extra_field/(?P<fid>\d+)/delete$', views.extra_field_delete, name='extra_field_delete'),
url(r'^(?P<uid>\d+)/extra_field/edit$', views.extra_field_change, name='extra_field_edit'), url(r'^(?P<uid>\d+)/extra_field/edit$', views.extra_field_change, name='extra_field_edit'),
url(r'^(?P<uid>\d+)/unsubscribe_service(?P<srvid>\d+)$', views.unsubscribe_service,
url(r'^(?P<uid>\d+)/unsubscribe_service(?P<abon_tariff_id>\d+)$', views.unsubscribe_service,
name='unsubscribe_service'), name='unsubscribe_service'),
url(r'^(?P<uid>\d+)/dev/$', views.dev, name='dev'), url(r'^(?P<uid>\d+)/dev/$', views.dev, name='dev'),
url(r'^(?P<uid>\d+)/clear_dev/$', views.clear_dev, name='clear_dev'), url(r'^(?P<uid>\d+)/clear_dev/$', views.clear_dev, name='clear_dev'),
url(r'^(?P<uid>\d+)/task_log$', views.task_log, name='task_log')
url(r'^(?P<uid>\d+)/task_log$', views.task_log, name='task_log'),
url(r'^(?P<uid>\d+)/user_dev$', views.save_user_dev_port, name='save_user_dev_port')
] ]

202
abonapp/views.py

@ -4,6 +4,7 @@ from django.contrib.gis.shortcuts import render_to_text
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.db import IntegrityError, ProgrammingError from django.db import IntegrityError, ProgrammingError
from django.db.models import Count, Q from django.db.models import Count, Q
from django.db.transaction import atomic
from django.shortcuts import render, redirect, get_object_or_404, resolve_url from django.shortcuts import render, redirect, get_object_or_404, resolve_url
from django.contrib.auth.decorators import login_required, permission_required from django.contrib.auth.decorators import login_required, permission_required
from django.utils import timezone from django.utils import timezone
@ -11,16 +12,17 @@ from django.http import HttpResponse
from django.contrib import messages from django.contrib import messages
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from statistics.models import getModel
from statistics.models import StatCache
from tariff_app.models import Tariff from tariff_app.models import Tariff
from agent import NasFailedResult, Transmitter, NasNetworkError from agent import NasFailedResult, Transmitter, NasNetworkError
from . import forms from . import forms
from . import models from . import models
import mydefs import mydefs
from devapp.models import Device
from devapp.models import Device, Port as DevPort
from datetime import datetime, date from datetime import datetime, date
from taskapp.models import Task from taskapp.models import Task
from dialing_app.models import AsteriskCDR from dialing_app.models import AsteriskCDR
from statistics.models import getModel, get_dates
@login_required @login_required
@ -33,8 +35,6 @@ def peoples(request, gid):
else: else:
peoples_list = peoples_list.filter(group=gid) peoples_list = peoples_list.filter(group=gid)
StatModel = getModel()
# фильтр # фильтр
dr, field = mydefs.order_helper(request) dr, field = mydefs.order_helper(request)
if field: if field:
@ -44,10 +44,10 @@ def peoples(request, gid):
peoples_list = mydefs.pag_mn(request, peoples_list) peoples_list = mydefs.pag_mn(request, peoples_list)
for abon in peoples_list: for abon in peoples_list:
if abon.ip_address is not None: if abon.ip_address is not None:
traf = StatModel.objects.traffic_by_ip(abon.ip_address)
if traf[1] is not None:
abon.traf = traf[1]
abon.is_online =traf[0]
try:
abon.stat_cache = StatCache.objects.get(ip=abon.ip_address)
except StatCache.DoesNotExist:
pass
except mydefs.LogicError as e: except mydefs.LogicError as e:
messages.warning(request, e) messages.warning(request, e)
@ -191,6 +191,7 @@ def delentity(request):
@login_required @login_required
@permission_required('abonapp.can_add_ballance') @permission_required('abonapp.can_add_ballance')
@atomic
def abonamount(request, gid, uid): def abonamount(request, gid, uid):
abon = get_object_or_404(models.Abon, pk=uid) abon = get_object_or_404(models.Abon, pk=uid)
try: try:
@ -244,12 +245,14 @@ def pay_history(request, gid, uid):
@login_required @login_required
@mydefs.only_admins @mydefs.only_admins
def abon_services(request, gid, uid): def abon_services(request, gid, uid):
grp = get_object_or_404(models.AbonGroup, pk=gid)
abon = get_object_or_404(models.Abon, pk=uid) abon = get_object_or_404(models.Abon, pk=uid)
return render(request, 'abonapp/service.html', { return render(request, 'abonapp/service.html', {
'abon': abon, 'abon': abon,
'abon_tariff': abon.current_tariff, 'abon_tariff': abon.current_tariff,
'abon_group': abon.group
'abon_group': abon.group,
'services': grp.tariffs.all()
}) })
@ -260,20 +263,13 @@ def abonhome(request, gid, uid):
abon_group = get_object_or_404(models.AbonGroup, pk=gid) abon_group = get_object_or_404(models.AbonGroup, pk=gid)
frm = None frm = None
passw = None passw = None
abon_device = None
try: try:
if request.method == 'POST': if request.method == 'POST':
if not request.user.has_perm('abonapp.change_abon'): if not request.user.has_perm('abonapp.change_abon'):
raise PermissionDenied raise PermissionDenied
frm = forms.AbonForm(request.POST, instance=abon) frm = forms.AbonForm(request.POST, instance=abon)
if frm.is_valid(): if frm.is_valid():
# если нет option82, т.е. динамический ip то не сохраняем изменения ip
if abon.opt82 is None:
ip_str = request.POST.get('ip')
if ip_str:
abon.ip_address = ip_str
else:
abon.ip_address = None
abon.ip_address = request.POST.get('ip')
frm.save() frm.save()
messages.success(request, _('edit abon success msg')) messages.success(request, _('edit abon success msg'))
else: else:
@ -281,7 +277,8 @@ def abonhome(request, gid, uid):
else: else:
passw = models.AbonRawPassword.objects.get(account=abon).passw_text passw = models.AbonRawPassword.objects.get(account=abon).passw_text
frm = forms.AbonForm(instance=abon, initial={'password': passw}) frm = forms.AbonForm(instance=abon, initial={'password': passw})
abon_device = models.AbonDevice.objects.get(abon=abon)
if abon.device is None:
messages.warning(request, _('User device was not found'))
except mydefs.LogicError as e: except mydefs.LogicError as e:
messages.error(request, e) messages.error(request, e)
passw = models.AbonRawPassword.objects.get(account=abon).passw_text passw = models.AbonRawPassword.objects.get(account=abon).passw_text
@ -291,8 +288,6 @@ def abonhome(request, gid, uid):
messages.error(request, e) messages.error(request, e)
except models.AbonRawPassword.DoesNotExist: except models.AbonRawPassword.DoesNotExist:
messages.warning(request, _('User has not have password, and cannot login')) messages.warning(request, _('User has not have password, and cannot login'))
except models.AbonDevice.DoesNotExist:
messages.warning(request, _('User device was not found'))
except mydefs.MultipleException as errs: except mydefs.MultipleException as errs:
for err in errs.err_list: for err in errs.err_list:
messages.add_message(request, messages.constants.ERROR, err) messages.add_message(request, messages.constants.ERROR, err)
@ -304,8 +299,8 @@ def abonhome(request, gid, uid):
'abon_group': abon_group, 'abon_group': abon_group,
'ip': abon.ip_address, 'ip': abon.ip_address,
'is_bad_ip': getattr(abon, 'is_bad_ip', False), 'is_bad_ip': getattr(abon, 'is_bad_ip', False),
'tech_form': forms.Opt82Form(instance=abon.opt82),
'device': abon_device.device if abon_device is not None else None
'device': abon.device,
'dev_ports': DevPort.objects.filter(device=abon.device) if abon.device else None
}) })
else: else:
return render(request, 'abonapp/viewAbon.html', { return render(request, 'abonapp/viewAbon.html', {
@ -316,40 +311,7 @@ def abonhome(request, gid, uid):
}) })
@login_required
@mydefs.only_admins
def opt82(request, gid, uid):
try:
abon = models.Abon.objects.get(pk=uid)
if request.method == 'POST':
try:
opt82_instance = models.Opt82.objects.get(
mac=request.POST.get('mac'),
port=request.POST.get('port')
)
except models.Opt82.DoesNotExist:
frm = forms.Opt82Form(request.POST)
if frm.is_valid():
opt82_instance = frm.save()
else:
messages.error(request, _('fix form errors'))
return redirect('abonapp:abon_home', gid=gid, uid=uid)
abon.opt82 = opt82_instance
else:
act = request.GET.get('act')
if act is not None and act == 'release':
if abon.opt82 is not None:
abon.opt82.delete()
abon.opt82 = None
abon.save(update_fields=['opt82'])
except models.Abon.DoesNotExist:
messages.error(request, _('User does not exist'))
return redirect('abonapp:abon_home', gid=gid, uid=uid)
@mydefs.require_ssl
@atomic
def terminal_pay(request): def terminal_pay(request):
from .pay_systems import allpay from .pay_systems import allpay
ret_text = allpay(request) ret_text = allpay(request)
@ -395,6 +357,7 @@ def add_invoice(request, gid, uid):
@login_required @login_required
@permission_required('abonapp.can_buy_tariff') @permission_required('abonapp.can_buy_tariff')
@atomic
def pick_tariff(request, gid, uid): def pick_tariff(request, gid, uid):
grp = get_object_or_404(models.AbonGroup, pk=gid) grp = get_object_or_404(models.AbonGroup, pk=gid)
abon = get_object_or_404(models.Abon, pk=uid) abon = get_object_or_404(models.Abon, pk=uid)
@ -426,12 +389,14 @@ def pick_tariff(request, gid, uid):
return render(request, 'abonapp/buy_tariff.html', { return render(request, 'abonapp/buy_tariff.html', {
'tariffs': tariffs, 'tariffs': tariffs,
'abon': abon, 'abon': abon,
'abon_group': grp
'abon_group': grp,
'selected_tariff': mydefs.safe_int(request.GET.get('selected_tariff'))
}) })
@login_required @login_required
@permission_required('abonapp.can_complete_service') @permission_required('abonapp.can_complete_service')
@atomic
def complete_service(request, gid, uid, srvid): def complete_service(request, gid, uid, srvid):
abtar = get_object_or_404(models.AbonTariff, pk=srvid) abtar = get_object_or_404(models.AbonTariff, pk=srvid)
abon = abtar.abon abon = abtar.abon
@ -489,12 +454,11 @@ def complete_service(request, gid, uid, srvid):
}) })
@login_required @login_required
@permission_required('abonapp.delete_abontariff') @permission_required('abonapp.delete_abontariff')
def unsubscribe_service(request, gid, uid, srvid):
def unsubscribe_service(request, gid, uid, abon_tariff_id):
try: try:
get_object_or_404(models.AbonTariff, pk=int(srvid)).delete()
get_object_or_404(models.AbonTariff, pk=int(abon_tariff_id)).delete()
messages.success(request, _('User has been detached from service')) messages.success(request, _('User has been detached from service'))
except NasFailedResult as e: except NasFailedResult as e:
messages.error(request, e) messages.error(request, e)
@ -503,7 +467,7 @@ def unsubscribe_service(request, gid, uid, srvid):
except mydefs.MultipleException as errs: except mydefs.MultipleException as errs:
for err in errs.err_list: for err in errs.err_list:
messages.add_message(request, messages.constants.ERROR, err) messages.add_message(request, messages.constants.ERROR, err)
return redirect('abonapp:abon_home', gid=gid, uid=uid)
return redirect('abonapp:abon_services', gid=gid, uid=uid)
@login_required @login_required
@ -569,11 +533,15 @@ def passport_view(request, gid, uid):
try: try:
abon = models.Abon.objects.get(pk=uid) abon = models.Abon.objects.get(pk=uid)
if request.method == 'POST': if request.method == 'POST':
frm = forms.PassportForm(request.POST)
try:
passport_instance = models.PassportInfo.objects.get(abon=abon)
except models.PassportInfo.DoesNotExist:
passport_instance = None
frm = forms.PassportForm(request.POST, instance=passport_instance)
if frm.is_valid(): if frm.is_valid():
passp_instance = frm.save(commit=False)
passp_instance.abon = abon
passp_instance.save()
pi = frm.save(commit=False)
pi.abon = abon
pi.save()
messages.success(request, _('Passport information has been saved')) messages.success(request, _('Passport information has been saved'))
return redirect('abonapp:passport_view', gid=gid, uid=uid) return redirect('abonapp:passport_view', gid=gid, uid=uid)
else: else:
@ -615,24 +583,20 @@ def chgroup_tariff(request, gid):
def dev(request, gid, uid): def dev(request, gid, uid):
abon_dev = None abon_dev = None
try: try:
abon = models.Abon.objects.get(pk=uid)
if request.method == 'POST': if request.method == 'POST':
dev = Device.objects.get(pk=request.POST.get('dev')) dev = Device.objects.get(pk=request.POST.get('dev'))
abon = models.Abon.objects.get(pk=uid)
try:
models.AbonDevice.objects.get(device=dev, abon=abon)
except models.AbonDevice.DoesNotExist:
models.AbonDevice.objects.create(abon=abon, device=dev)
abon.device = dev
abon.save(update_fields=['device'])
messages.success(request, _('Device has successfully attached')) messages.success(request, _('Device has successfully attached'))
return redirect('abonapp:abon_home', gid=gid, uid=uid) return redirect('abonapp:abon_home', gid=gid, uid=uid)
else: else:
abon_dev = models.AbonDevice.objects.get(abon=uid).device
abon_dev = abon.device
except Device.DoesNotExist: except Device.DoesNotExist:
messages.warning(request, _('Device your selected already does not exist')) messages.warning(request, _('Device your selected already does not exist'))
except models.Abon.DoesNotExist: except models.Abon.DoesNotExist:
messages.error(request, _('Abon does not exist')) messages.error(request, _('Abon does not exist'))
return redirect('abonapp:people_list', gid=gid) return redirect('abonapp:people_list', gid=gid)
except models.AbonDevice.DoesNotExist:
messages.warning(request, _('User device was not found'))
return render(request, 'abonapp/modal_dev.html', { return render(request, 'abonapp/modal_dev.html', {
'devices': Device.objects.filter(user_group=gid), 'devices': Device.objects.filter(user_group=gid),
'dev': abon_dev, 'dev': abon_dev,
@ -645,8 +609,8 @@ def dev(request, gid, uid):
def clear_dev(request, gid, uid): def clear_dev(request, gid, uid):
try: try:
abon = models.Abon.objects.get(pk=uid) abon = models.Abon.objects.get(pk=uid)
abdev = models.AbonDevice.objects.get(abon=abon)
abdev.delete()
abon.device = None
abon.save(update_fields=['device'])
messages.success(request, _('Device has successfully unattached')) messages.success(request, _('Device has successfully unattached'))
except models.Abon.DoesNotExist: except models.Abon.DoesNotExist:
messages.error(request, _('Abon does not exist')) messages.error(request, _('Abon does not exist'))
@ -657,7 +621,6 @@ def clear_dev(request, gid, uid):
@login_required @login_required
@mydefs.only_admins @mydefs.only_admins
def charts(request, gid, uid): def charts(request, gid, uid):
from statistics.models import getModel
high = 100 high = 100
wandate = request.GET.get('wantdate') wandate = request.GET.get('wantdate')
@ -705,7 +668,7 @@ def charts(request, gid, uid):
'abon': abon, 'abon': abon,
'charts_data': ',\n'.join(charts_data) if charts_data is not None else None, 'charts_data': ',\n'.join(charts_data) if charts_data is not None else None,
'high': high, 'high': high,
'dates': StatElem.objects.get_dates()
'dates': get_dates()
}) })
@ -809,7 +772,6 @@ def abon_ping(request):
def dials(request, gid, uid): def dials(request, gid, uid):
abon = get_object_or_404(models.Abon, pk=uid) abon = get_object_or_404(models.Abon, pk=uid)
if hasattr(abon.group, 'pk') and abon.group.pk != int(gid): if hasattr(abon.group, 'pk') and abon.group.pk != int(gid):
print(gid, type(gid), abon.group.pk, type(abon.group.pk))
return redirect('abonapp:dials', abon.group.pk, abon.pk) return redirect('abonapp:dials', abon.group.pk, abon.pk)
if abon.telephone is not None and abon.telephone != '': if abon.telephone is not None and abon.telephone != '':
tel = abon.telephone.replace('+', '') tel = abon.telephone.replace('+', '')
@ -826,6 +788,88 @@ def dials(request, gid, uid):
}) })
@login_required
@mydefs.only_admins
def save_user_dev_port(request, gid, uid):
if request.method != 'POST':
messages.error(request, _('Method is not POST'))
return redirect('abonapp:abon_home', gid, uid)
user_port = mydefs.safe_int(request.POST.get('user_port'))
is_dynamic_ip = request.POST.get('is_dynamic_ip')
try:
if user_port == 0:
port = None
else:
port = DevPort.objects.get(pk=user_port)
abon = models.Abon.objects.get(pk=uid)
abon.dev_port = port
if abon.is_dynamic_ip != is_dynamic_ip:
abon.is_dynamic_ip = is_dynamic_ip
abon.save(update_fields=['dev_port', 'is_dynamic_ip'])
else:
abon.save(update_fields=['dev_port'])
messages.success(request, _('User port has been saved'))
except DevPort.DoesNotExist:
messages.error(request, _('Selected port does not exist'))
except models.Abon.DoesNotExist:
messages.error(request, _('User does not exist'))
return redirect('abonapp:abon_home', gid, uid)
@login_required
@permission_required('abonapp.add_abonstreet')
def street_add(request, gid):
if request.method == 'POST':
frm = forms.AbonStreetForm(request.POST)
if frm.is_valid():
frm.save()
messages.success(request, _('Street successfully saved'))
return redirect('abonapp:people_list', gid)
else:
messages.error(request, _('fix form errors'))
else:
frm = forms.AbonStreetForm(initial={'group': gid})
return render_to_text('abonapp/modal_addstreet.html', {
'form': frm,
'gid': gid
}, request=request)
@login_required
@permission_required('abonapp.change_abonstreet')
def street_edit(request, gid):
try:
if request.method == 'POST':
streets_pairs = [(int(sid), sname) for sid, sname in zip(request.POST.getlist('sid'), request.POST.getlist('sname'))]
for sid, sname in streets_pairs:
street = models.AbonStreet.objects.get(pk=sid)
street.name = sname
street.save()
messages.success(request, _('Streets has been saved'))
else:
return render_to_text('abonapp/modal_editstreet.html', {
'gid': gid,
'streets': models.AbonStreet.objects.filter(group=gid)
}, request=request)
except models.AbonStreet.DoesNotExist:
messages.error(request, _('One of these streets has not been found'))
return redirect('abonapp:people_list', gid)
@login_required
@permission_required('abonapp.delete_abonstreet')
def street_del(request, gid, sid):
try:
models.AbonStreet.objects.get(pk=sid, group=gid).delete()
messages.success(request, _('The street successfully deleted'))
except models.AbonStreet.DoesNotExist:
messages.error(request, _('The street has not been found'))
return redirect('abonapp:people_list', gid)
# API's # API's
def abons(request): def abons(request):
@ -853,5 +897,5 @@ def abons(request):
def search_abon(request): def search_abon(request):
word = request.GET.get('s') word = request.GET.get('s')
results = models.Abon.objects.filter(fio__icontains=word)[:8] results = models.Abon.objects.filter(fio__icontains=word)[:8]
results = [{'id': usr.pk, 'name': usr.username, 'fio': usr.fio} for usr in results]
results = [{'id': usr.pk, 'text': "%s: %s" % (usr.username, usr.fio)} for usr in results]
return HttpResponse(dumps(results, ensure_ascii=False)) return HttpResponse(dumps(results, ensure_ascii=False))

32
accounts_app/locale/ru/LC_MESSAGES/django.po

@ -23,10 +23,6 @@ msgstr ""
msgid "Users must have an telephone number" msgid "Users must have an telephone number"
msgstr "У пользователей должен быть номер телефона" msgstr "У пользователей должен быть номер телефона"
#: accounts_app/models.py:52
msgid "Telephone number"
msgstr "Номер телефона"
#: accounts_app/templates/accounts/group.html:7 #: accounts_app/templates/accounts/group.html:7
#: accounts_app/templates/accounts/group_list.html:7 #: accounts_app/templates/accounts/group_list.html:7
msgid "Administrators" msgid "Administrators"
@ -81,7 +77,7 @@ msgstr "Добавить группу"
#: accounts_app/templates/accounts/index.html:8 #: accounts_app/templates/accounts/index.html:8
#: accounts_app/templates/accounts/settings/ch_info.html:37 #: accounts_app/templates/accounts/settings/ch_info.html:37
msgid "Telephone" msgid "Telephone"
msgstr "Номер телефона"
msgstr "Телефон"
#: accounts_app/templates/accounts/index.html:12 #: accounts_app/templates/accounts/index.html:12
#: accounts_app/templates/accounts/login.html:35 #: accounts_app/templates/accounts/login.html:35
@ -140,27 +136,35 @@ msgstr "Старый пароль"
msgid "New password" msgid "New password"
msgstr "Новый пароль" msgstr "Новый пароль"
#: accounts_app/views.py:43
#: accounts_app/views.py:42
msgid "Wrong login or password, please try again" msgid "Wrong login or password, please try again"
msgstr "Неправильный логин или пароль, попробуйте ещё раз" msgstr "Неправильный логин или пароль, попробуйте ещё раз"
#: accounts_app/views.py:137
#: accounts_app/views.py:141
msgid "New password is empty, fill it"
msgstr "Новый пароль пустой, придумайте себе пароль"
#: accounts_app/views.py:143
msgid "Wrong password" msgid "Wrong password"
msgstr "Неправильный пароль" msgstr "Неправильный пароль"
#: accounts_app/views.py:162
#: accounts_app/views.py:145
msgid "Empty password, fill it"
msgstr "Пустой пароль, впишите что-то в пароль"
#: accounts_app/views.py:168
msgid "You forget specify a password for the new account" msgid "You forget specify a password for the new account"
msgstr "Забыли указать пароль для нового аккаунта" msgstr "Забыли указать пароль для нового аккаунта"
#: accounts_app/views.py:165
#: accounts_app/views.py:171
msgid "You forget to repeat a password for the new account" msgid "You forget to repeat a password for the new account"
msgstr "Забыли повторить пароль для нового аккаунта" msgstr "Забыли повторить пароль для нового аккаунта"
#: accounts_app/views.py:174
#: accounts_app/views.py:180
msgid "Subscriber with this name already exist" msgid "Subscriber with this name already exist"
msgstr "Пользователь с таким именем уже есть" msgstr "Пользователь с таким именем уже есть"
#: accounts_app/views.py:176
#: accounts_app/views.py:182
msgid "Passwords does not match, try again" msgid "Passwords does not match, try again"
msgstr "Пароли не совпадают, попробуйте ещё раз" msgstr "Пароли не совпадают, попробуйте ещё раз"
@ -184,3 +188,9 @@ msgstr "Редактировать"
msgid "Set a task" msgid "Set a task"
msgstr "Дать задачу" msgstr "Дать задачу"
msgid "Please select an image"
msgstr "Пожалуйста выберите изображение"
msgid "Avatar successfully changed"
msgstr "Аватар успешно изменён"

21
accounts_app/migrations/0007_auto_20170816_1109.py

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2017-08-16 11:09
from __future__ import unicode_literals
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts_app', '0006_auto_20170416_1029'),
]
operations = [
migrations.AlterField(
model_name='userprofile',
name='telephone',
field=models.CharField(max_length=16, validators=[django.core.validators.RegexValidator('^\\+[7,8,9,3]\\d{10,11}$')], verbose_name='Телефон'),
),
]

2
accounts_app/models.py

@ -49,7 +49,7 @@ class UserProfile(AbstractBaseUser, PermissionsMixin):
is_admin = models.BooleanField(default=False) is_admin = models.BooleanField(default=False)
telephone = models.CharField( telephone = models.CharField(
max_length=16, max_length=16,
verbose_name=_('Telephone number'),
verbose_name=_('Telephone'),
#unique=True, #unique=True,
validators=[RegexValidator('^\+[7,8,9,3]\d{10,11}$')] validators=[RegexValidator('^\+[7,8,9,3]\d{10,11}$')]
) )

2
accounts_app/templates/accounts/index.html

@ -2,6 +2,7 @@
{% load i18n %} {% load i18n %}
{% block content %} {% block content %}
<div class="table-responsive">
<table class="table table-striped table-bordered"> <table class="table table-striped table-bordered">
<tbody> <tbody>
<tr> <tr>
@ -32,5 +33,6 @@
{% endif %} {% endif %}
</tbody> </tbody>
</table> </table>
</div>
{% endblock %} {% endblock %}

21
accounts_app/views.py

@ -4,7 +4,6 @@ from django.contrib.auth import authenticate, login, logout
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.core.urlresolvers import NoReverseMatch from django.core.urlresolvers import NoReverseMatch
from django.shortcuts import render, redirect, get_object_or_404, resolve_url from django.shortcuts import render, redirect, get_object_or_404, resolve_url
from django.http import Http404
from django.contrib.auth.models import Group, Permission from django.contrib.auth.models import Group, Permission
from django.contrib import messages from django.contrib import messages
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
@ -103,15 +102,20 @@ def chgroup(request, uid):
@mydefs.only_admins @mydefs.only_admins
def ch_ava(request): def ch_ava(request):
if request.method == 'POST': if request.method == 'POST':
phname = request.FILES.get('avatar')
if phname is None:
messages.error(request, _('Please select an image'))
else:
user = request.user user = request.user
if user.avatar: if user.avatar:
user.avatar.delete() user.avatar.delete()
photo = Photo() photo = Photo()
photo.image = request.FILES.get('avatar')
photo.image = phname
photo.save() photo.save()
user.avatar = photo user.avatar = photo
user.save(update_fields=['avatar']) user.save(update_fields=['avatar'])
request.user = user request.user = user
messages.success(request, _('Avatar successfully changed'))
return render(request, 'accounts/settings/ch_info.html', { return render(request, 'accounts/settings/ch_info.html', {
'user': request.user 'user': request.user
@ -129,14 +133,21 @@ def ch_info(request):
user.telephone = request.POST.get('telephone') user.telephone = request.POST.get('telephone')
psw = request.POST.get('oldpasswd') psw = request.POST.get('oldpasswd')
if psw != '':
if psw != '' and psw is not None:
if user.check_password(psw): if user.check_password(psw):
newpasswd = request.POST.get('newpasswd') newpasswd = request.POST.get('newpasswd')
if newpasswd != '' and newpasswd is not None:
user.set_password(newpasswd) user.set_password(newpasswd)
else:
messages.error(request, _('Wrong password'))
user.save() user.save()
request.user = user request.user = user
logout(request)
return redirect('acc_app:other_profile', uid=user.pk)
else:
messages.error(request, _('New password is empty, fill it'))
else:
messages.error(request, _('Wrong password'))
else:
messages.warning(request, _('Empty password, fill it'))
return render(request, 'accounts/settings/ch_info.html', { return render(request, 'accounts/settings/ch_info.html', {
'user': request.user 'user': request.user

44
agent/commands/dhcp.py

@ -1,33 +1,39 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from django.core.exceptions import MultipleObjectsReturned from django.core.exceptions import MultipleObjectsReturned
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from abonapp.models import Abon, Opt82
def get_82_opts(switch_mac, switch_port):
try:
opt82 = Opt82.objects.get(mac=switch_mac, port=switch_port)
except MultipleObjectsReturned:
Opt82.objects.filter(mac=switch_mac, port=switch_port)[1:].delete()
return get_82_opts(switch_mac, switch_port)
except Opt82.DoesNotExist:
opt82 = Opt82.objects.create(mac=switch_mac, port=switch_port)
return opt82
from abonapp.models import Abon
from devapp.models import Device, Port
def dhcp_commit(client_ip, client_mac, switch_mac, switch_port): def dhcp_commit(client_ip, client_mac, switch_mac, switch_port):
opt82 = get_82_opts(switch_mac, switch_port)
if opt82 is None:
print(_("ERROR: opt82 with mac:%s and port:%d does not exist in db") % (switch_mac, switch_port))
return
try: try:
abon = Abon.objects.get(opt82=opt82)
dev = Device.objects.get(mac_addr=switch_mac)
mngr_class = dev.get_manager_klass()
port = _('<never mind>')
if mngr_class.is_use_device_port():
port = Port.objects.get(device=dev, num=switch_port)
abon = Abon.objects.get(dev_port=port, device=dev)
else:
abon = Abon.objects.get(device=dev)
if not abon.is_dynamic_ip:
print('D:', _('User settings is not dynamic'))
return
if not abon.is_access():
print('D:', _('User is not access to service'))
return
abon.ip_address = client_ip abon.ip_address = client_ip
abon.is_dhcp = True abon.is_dhcp = True
abon.save(update_fields=['ip_address']) abon.save(update_fields=['ip_address'])
print(_('Ip address update for %s successfull') % abon.get_short_name())
print('S:', _("Ip address:'%s' update for '%s' successfull, on port: %s") % (client_ip, abon.get_short_name(), port))
except Abon.DoesNotExist: except Abon.DoesNotExist:
print('ERROR: abon with option82(%s-%d) does not exist' % (opt82.mac, opt82.port))
print('N:', _("User with device '%s' does not exist") % dev)
except Device.DoesNotExist:
print('N:', _('Device with mac %s not found') % switch_mac)
except Port.DoesNotExist:
print('N:', _('Port %d on device with mac %s does not exist') % (int(switch_port), switch_mac))
except MultipleObjectsReturned as e:
print('E:', 'MultipleObjectsReturned:', type(e), e)
def dhcp_expiry(client_ip): def dhcp_expiry(client_ip):

31
agent/core.py

@ -99,3 +99,34 @@ class BaseTransmitter(metaclass=ABCMeta):
:param count: количество пингов :param count: количество пингов
:return: None если не пингуется, иначе кортеж, в котором (сколько вернулось, сколько было отправлено) :return: None если не пингуется, иначе кортеж, в котором (сколько вернулось, сколько было отправлено)
""" """
@abstractmethod
def read_users(self):
"""
Читаем пользователей с NAS
:return: список AbonStruct
"""
def _diff_users(self, users_from_db):
"""
:param users_from_db: QuerySet всех абонентов у которых может быть обслуживание
:return: на выходе получаем абонентов которых надо добавить в nas и которых надо удалить
"""
users_from_db = [ab.build_agent_struct() for ab in users_from_db if ab.is_access()]
users_from_db = set([ab for ab in users_from_db if ab is not None and ab.tariff is not None])
users_from_nas = set(self.read_users())
list_for_del = (users_from_db ^ users_from_nas) - users_from_db
list_for_add = users_from_db - users_from_nas
return list_for_add, list_for_del
def sync_nas(self, users_from_db):
list_for_add, list_for_del = self._diff_users(users_from_db)
print('FOR DELETE')
for ld in list_for_del:
print(ld)
print('FOR ADD')
for la in list_for_add:
print(la)
self.remove_user_range( list_for_del )
self.add_user_range( list_for_add )

106
agent/mod_mikrotik.py

@ -5,13 +5,13 @@ from abc import ABCMeta
from hashlib import md5 from hashlib import md5
from .core import BaseTransmitter, NasFailedResult, NasNetworkError from .core import BaseTransmitter, NasFailedResult, NasNetworkError
from mydefs import ping from mydefs import ping
from .structs import TariffStruct, AbonStruct, IpStruct, ShapeItem
from .structs import TariffStruct, AbonStruct, IpStruct
from . import settings from . import settings
from djing.settings import DEBUG from djing.settings import DEBUG
import re import re
#DEBUG=False
#DEBUG=True
LIST_USERS_ALLOWED = 'DjingUsersAllowed' LIST_USERS_ALLOWED = 'DjingUsersAllowed'
LIST_USERS_BLOCKED = 'DjingUsersBlocked' LIST_USERS_BLOCKED = 'DjingUsersBlocked'
@ -190,28 +190,29 @@ class TransmitterManager(BaseTransmitter, metaclass=ABCMeta):
res = text_speed_digit res = text_speed_digit
elif text_append == 'k': elif text_append == 'k':
res = text_speed_digit / 1000 res = text_speed_digit / 1000
#elif text_append == 'G':
# elif text_append == 'G':
# res = text_speed_digit * 0x400 # res = text_speed_digit * 0x400
else: else:
res = float(re.sub(r'[a-zA-Z]', '', text_speed)) / 1000**2
res = float(re.sub(r'[a-zA-Z]', '', text_speed)) / 1000 ** 2
return res return res
try:
speeds = info['=max-limit'].split('/') speeds = info['=max-limit'].split('/')
t = TariffStruct( t = TariffStruct(
speedIn=parse_speed(speeds[1]), speedIn=parse_speed(speeds[1]),
speedOut=parse_speed(speeds[0]) speedOut=parse_speed(speeds[0])
) )
try:
a = AbonStruct( a = AbonStruct(
uid=int(info['=name'][3:]), uid=int(info['=name'][3:]),
#FIXME: тут в разных микротиках или =target-addresses или =target
# FIXME: тут в разных микротиках или =target-addresses или =target
ip=info['=target'][:-3], ip=info['=target'][:-3],
tariff=t, tariff=t,
is_active=False if info['=disabled'] == 'false' else True is_active=False if info['=disabled'] == 'false' else True
) )
return ShapeItem(abon=a, sid=info['=.id'].replace('*', ''))
except KeyError:
return
a.queue_id = info['=.id']
return a
except ValueError:
pass
class QueueManager(TransmitterManager, metaclass=ABCMeta): class QueueManager(TransmitterManager, metaclass=ABCMeta):
@ -223,11 +224,12 @@ class QueueManager(TransmitterManager, metaclass=ABCMeta):
def add(self, user): def add(self, user):
assert isinstance(user, AbonStruct) assert isinstance(user, AbonStruct)
assert isinstance(user.tariff, TariffStruct)
if user.tariff is None or not isinstance(user.tariff, TariffStruct):
return
return self._exec_cmd(['/queue/simple/add', return self._exec_cmd(['/queue/simple/add',
'=name=uid%d' % user.uid, '=name=uid%d' % user.uid,
#FIXME: тут в разных микротиках или =target-addresses или =target
'=target=%s' % user.ip.get_str(),
# FIXME: тут в разных микротиках или =target-addresses или =target
'=target=%s' % str(user.ip),
'=max-limit=%.3fM/%.3fM' % (user.tariff.speedOut, user.tariff.speedIn), '=max-limit=%.3fM/%.3fM' % (user.tariff.speedOut, user.tariff.speedIn),
'=queue=MikroBILL_SFQ/MikroBILL_SFQ', '=queue=MikroBILL_SFQ/MikroBILL_SFQ',
'=burst-time=1/1' '=burst-time=1/1'
@ -237,26 +239,28 @@ class QueueManager(TransmitterManager, metaclass=ABCMeta):
assert isinstance(user, AbonStruct) assert isinstance(user, AbonStruct)
q = self.find('uid%d' % user.uid) q = self.find('uid%d' % user.uid)
if q is not None: if q is not None:
return self._exec_cmd(['/queue/simple/remove', '=.id=*' + str(q.sid)])
return self._exec_cmd(['/queue/simple/remove', '=.id=' + getattr(q, 'queue_id', '')])
def remove_range(self, q_ids): def remove_range(self, q_ids):
names = ['%d' % usr for usr in q_ids]
return self._exec_cmd(['/queue/simple/remove'] + names)
if q_ids is not None and len(q_ids) > 0:
return self._exec_cmd(['/queue/simple/remove', '=numbers=' + ','.join(q_ids)])
def update(self, user): def update(self, user):
assert isinstance(user, AbonStruct) assert isinstance(user, AbonStruct)
if user.tariff is None or not isinstance(user.tariff, TariffStruct):
return
queue = self.find('uid%d' % user.uid) queue = self.find('uid%d' % user.uid)
if queue is None: if queue is None:
# не нашли запись в шейпере об абоненте, добавим # не нашли запись в шейпере об абоненте, добавим
return self.add(user) return self.add(user)
else: else:
mk_id = queue.sid
mk_id = getattr(queue, 'queue_id', '')
# обновляем шейпер абонента # обновляем шейпер абонента
return self._exec_cmd(['/queue/simple/set', '=.id=*' + mk_id,
return self._exec_cmd(['/queue/simple/set', '=.id=' + mk_id,
'=name=uid%d' % user.uid, '=name=uid%d' % user.uid,
'=max-limit=%.3fM/%.3fM' % (user.tariff.speedOut, user.tariff.speedIn), '=max-limit=%.3fM/%.3fM' % (user.tariff.speedOut, user.tariff.speedIn),
#FIXME: тут в разных микротиках или =target-addresses или =target
'=target=%s' % user.ip.get_str(),
# FIXME: тут в разных микротиках или =target-addresses или =target
'=target=%s' % str(user.ip),
'=queue=MikroBILL_SFQ/MikroBILL_SFQ', '=queue=MikroBILL_SFQ/MikroBILL_SFQ',
'=burst-time=1/1' '=burst-time=1/1'
]) ])
@ -266,7 +270,9 @@ class QueueManager(TransmitterManager, metaclass=ABCMeta):
queues = self._exec_cmd_iter(['/queue/simple/print', '=detail']) queues = self._exec_cmd_iter(['/queue/simple/print', '=detail'])
for queue in queues: for queue in queues:
if queue[0] == '!done': return if queue[0] == '!done': return
yield self._build_shape_obj(queue[1])
sobj = self._build_shape_obj(queue[1])
if sobj is not None:
yield sobj
# то же что и выше, только получаем только номера в микротике # то же что и выше, только получаем только номера в микротике
def read_mikroids_iter(self): def read_mikroids_iter(self):
@ -282,7 +288,7 @@ class QueueManager(TransmitterManager, metaclass=ABCMeta):
self.add(user) self.add(user)
return self.disable(user) return self.disable(user)
else: else:
return self._exec_cmd(['/queue/simple/disable', '=.id=*' + q.sid])
return self._exec_cmd(['/queue/simple/disable', '=.id=*' + getattr(q, 'queue_id', '')])
def enable(self, user): def enable(self, user):
assert isinstance(user, AbonStruct) assert isinstance(user, AbonStruct)
@ -291,7 +297,13 @@ class QueueManager(TransmitterManager, metaclass=ABCMeta):
self.add(user) self.add(user)
self.enable(user) self.enable(user)
else: else:
return self._exec_cmd(['/queue/simple/enable', '=.id=*' + q.sid])
return self._exec_cmd(['/queue/simple/enable', '=.id=*' + getattr(q, 'queue_id', '')])
class IpAddressListObj(IpStruct):
def __init__(self, ip, mk_id):
super(IpAddressListObj, self).__init__(ip)
self.mk_id = str(mk_id).replace('*', '')
class IpAddressListManager(TransmitterManager, metaclass=ABCMeta): class IpAddressListManager(TransmitterManager, metaclass=ABCMeta):
@ -301,7 +313,7 @@ class IpAddressListManager(TransmitterManager, metaclass=ABCMeta):
commands = [ commands = [
'/ip/firewall/address-list/add', '/ip/firewall/address-list/add',
'=list=%s' % list_name, '=list=%s' % list_name,
'=address=%s' % ip.get_str()
'=address=%s' % str(ip)
] ]
if type(timeout) is int: if type(timeout) is int:
commands.append('=timeout=%d' % timeout) commands.append('=timeout=%d' % timeout)
@ -318,7 +330,15 @@ class IpAddressListManager(TransmitterManager, metaclass=ABCMeta):
def remove(self, mk_id): def remove(self, mk_id):
return self._exec_cmd([ return self._exec_cmd([
'/ip/firewall/address-list/remove', '/ip/firewall/address-list/remove',
'=.id=*' + str(mk_id)
'=.id=*' + str(mk_id).replace('*', '')
])
def remove_range(self, items):
ids = [ip.mk_id for ip in items if isinstance(ip, IpAddressListObj)]
if len(ids) > 0:
return self._exec_cmd([
'/ip/firewall/address-list/remove',
'numbers=' + ','.join(ids)
]) ])
def find(self, ip, list_name): def find(self, ip, list_name):
@ -326,7 +346,13 @@ class IpAddressListManager(TransmitterManager, metaclass=ABCMeta):
return self._exec_cmd([ return self._exec_cmd([
'/ip/firewall/address-list/print', 'where', '/ip/firewall/address-list/print', 'where',
'?list=%s' % list_name, '?list=%s' % list_name,
'?address=%s' % ip.get_str()
'?address=%s' % str(ip)
])
def read_ips_iter(self, list_name):
return self._exec_cmd([
'/ip/firewall/address-list/print', 'where',
'?list=%s' % list_name
]) ])
def disable(self, user): def disable(self, user):
@ -355,18 +381,18 @@ class MikrotikTransmitter(QueueManager, IpAddressListManager):
self.add_user(usr) self.add_user(usr)
def remove_user_range(self, users): def remove_user_range(self, users):
queues = [QueueManager.find(self, 'uid%d' % user.uid) for user in users if isinstance(user, AbonStruct)]
queue_names = ["uid%d" % queue.sid for queue in queues]
QueueManager.remove_range(self, queue_names)
queue_ids = [usr.queue_id for usr in users if usr is not None]
QueueManager.remove_range(self, queue_ids)
ips = [user.ip for user in users if isinstance(user, AbonStruct)] ips = [user.ip for user in users if isinstance(user, AbonStruct)]
for ip in ips: for ip in ips:
ip_list_entity = IpAddressListManager.find(self, ip, LIST_USERS_ALLOWED) ip_list_entity = IpAddressListManager.find(self, ip, LIST_USERS_ALLOWED)
if len(ip_list_entity) > 1:
if ip_list_entity is not None and len(ip_list_entity) > 1:
IpAddressListManager.remove(self, ip_list_entity[0]['=.id']) IpAddressListManager.remove(self, ip_list_entity[0]['=.id'])
def add_user(self, user, ip_timeout=None): def add_user(self, user, ip_timeout=None):
assert isinstance(user.tariff, TariffStruct)
assert isinstance(user.ip, IpStruct) assert isinstance(user.ip, IpStruct)
if user.tariff is None or not isinstance(user.tariff, TariffStruct):
return
QueueManager.add(self, user) QueueManager.add(self, user)
IpAddressListManager.add(self, LIST_USERS_ALLOWED, user.ip, ip_timeout) IpAddressListManager.add(self, LIST_USERS_ALLOWED, user.ip, ip_timeout)
# удаляем из списка заблокированных абонентов # удаляем из списка заблокированных абонентов
@ -377,12 +403,11 @@ class MikrotikTransmitter(QueueManager, IpAddressListManager):
def remove_user(self, user): def remove_user(self, user):
QueueManager.remove(self, user) QueueManager.remove(self, user)
firewall_ip_list_obj = IpAddressListManager.find(self, user.ip, LIST_USERS_ALLOWED) firewall_ip_list_obj = IpAddressListManager.find(self, user.ip, LIST_USERS_ALLOWED)
if len(firewall_ip_list_obj) > 1:
if firewall_ip_list_obj is not None and len(firewall_ip_list_obj) > 1:
IpAddressListManager.remove(self, firewall_ip_list_obj[0]['=.id']) IpAddressListManager.remove(self, firewall_ip_list_obj[0]['=.id'])
# обновляем основную инфу абонента # обновляем основную инфу абонента
def update_user(self, user, ip_timeout=None): def update_user(self, user, ip_timeout=None):
assert isinstance(user.tariff, TariffStruct)
assert isinstance(user.ip, IpStruct) assert isinstance(user.ip, IpStruct)
# ищем ip абонента в списке ip # ищем ip абонента в списке ip
@ -396,6 +421,13 @@ class MikrotikTransmitter(QueueManager, IpAddressListManager):
IpAddressListManager.remove(self, find_res[0]['=.id']) IpAddressListManager.remove(self, find_res[0]['=.id'])
return return
# если нет услуги то её не должно быть и в nas
if user.tariff is None or not isinstance(user.tariff, TariffStruct):
queue = QueueManager.find(self, 'uid%d' % user.uid)
if queue is not None:
QueueManager.remove(self, user)
return
# если не найден (mikrotik возвращает пустой словарь в списке если ничего нет) # если не найден (mikrotik возвращает пустой словарь в списке если ничего нет)
if len(find_res) < 2: if len(find_res) < 2:
# добавим запись об абоненте # добавим запись об абоненте
@ -410,7 +442,7 @@ class MikrotikTransmitter(QueueManager, IpAddressListManager):
if queue is None: if queue is None:
QueueManager.add(self, user) QueueManager.add(self, user)
return return
if queue.abon != user:
if queue != user:
QueueManager.update(self, user) QueueManager.update(self, user)
def ping(self, host, count=10): def ping(self, host, count=10):
@ -430,7 +462,7 @@ class MikrotikTransmitter(QueueManager, IpAddressListManager):
# приостановливаем обслуживание абонента # приостановливаем обслуживание абонента
def pause_user(self, user): def pause_user(self, user):
pass
self.remove_user(user)
# продолжаем обслуживание абонента # продолжаем обслуживание абонента
def start_user(self, user): def start_user(self, user):
@ -454,3 +486,9 @@ class MikrotikTransmitter(QueueManager, IpAddressListManager):
def remove_tariff(self, tid): def remove_tariff(self, tid):
pass pass
def read_users(self):
# shapes is ShapeItem
# allowed_ips = IpAddressListManager.read_ips_iter(self, LIST_USERS_ALLOWED)
queues = QueueManager.read_queue_iter(self)
return queues

6
agent/netflow/djing_flow.conf

@ -1,6 +0,0 @@
# Config for djing flow
version = "0.1";
# количество строк на запрос
mysql_rows_per_request = 65735;

35
agent/structs.py

@ -34,9 +34,6 @@ class IpStruct(BaseStruct):
self.__ip = int(dt[0]) self.__ip = int(dt[0])
return self return self
def get_str(self):
return int2ip(self.__ip)
def get_int(self): def get_int(self):
return self.__ip return self.__ip
@ -44,22 +41,32 @@ class IpStruct(BaseStruct):
assert isinstance(other, IpStruct) assert isinstance(other, IpStruct)
return self.__ip == other.__ip return self.__ip == other.__ip
def __int__(self):
return self.__ip
def __str__(self): def __str__(self):
return int2ip(self.__ip) return int2ip(self.__ip)
def __hash__(self):
return hash(self.__ip)
# Как обслуживается абонент # Как обслуживается абонент
class TariffStruct(BaseStruct): class TariffStruct(BaseStruct):
def __init__(self, tariff_id=0, speedIn=None, speedOut=None): def __init__(self, tariff_id=0, speedIn=None, speedOut=None):
self.tid = tariff_id
self.speedIn = speedIn if speedIn is not None else 0.001
self.speedOut = speedOut if speedOut is not None else 0.001
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)
def serialize(self): def serialize(self):
dt = pack("!Iff", int(self.tid), float(self.speedIn), float(self.speedOut)) dt = pack("!Iff", int(self.tid), float(self.speedIn), float(self.speedOut))
return dt return dt
# Да, если все значения нулевые
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, *args):
dt = unpack("!Iff", data) dt = unpack("!Iff", data)
self.tid = int(dt[0]) self.tid = int(dt[0])
@ -68,7 +75,6 @@ class TariffStruct(BaseStruct):
return self return self
def __eq__(self, other): def __eq__(self, other):
assert isinstance(other, TariffStruct)
# не сравниваем id, т.к. тарифы с одинаковыми скоростями для NAS одинаковы # не сравниваем id, т.к. тарифы с одинаковыми скоростями для NAS одинаковы
# Да и иногда не удобно доставать из nas id тарифы из базы # Да и иногда не удобно доставать из nas id тарифы из базы
return self.speedIn == other.speedIn and self.speedOut == other.speedOut return self.speedIn == other.speedIn and self.speedOut == other.speedOut
@ -76,6 +82,11 @@ class TariffStruct(BaseStruct):
def __str__(self): def __str__(self):
return "Id=%d, speedIn=%.2f, speedOut=%.2f" % (self.tid, self.speedIn, self.speedOut) return "Id=%d, speedIn=%.2f, speedOut=%.2f" % (self.tid, self.speedIn, self.speedOut)
# нужно чтоб хеши тарифов In10,Out20 и In20,Out10 были разными
# поэтому сначала float->str и потом хеш
def __hash__(self):
return hash(str(self.speedIn) + str(self.speedOut))
# Абонент из базы # Абонент из базы
class AbonStruct(BaseStruct): class AbonStruct(BaseStruct):
@ -83,14 +94,15 @@ class AbonStruct(BaseStruct):
def __init__(self, uid=None, ip=None, tariff=None, is_active=True): def __init__(self, uid=None, ip=None, tariff=None, is_active=True):
self.uid = int(uid) self.uid = int(uid)
self.ip = IpStruct(ip) self.ip = IpStruct(ip)
assert isinstance(tariff, TariffStruct)
self.tariff = tariff self.tariff = tariff
self.is_active = is_active self.is_active = is_active
def serialize(self): def serialize(self):
if self.tariff is None:
return
assert isinstance(self.tariff, TariffStruct) assert isinstance(self.tariff, TariffStruct)
assert isinstance(self.ip, IpStruct) assert isinstance(self.ip, IpStruct)
dt = pack("!LII?", self.uid, self.ip.get_int(), self.tariff.tid, self.is_active)
dt = pack("!LII?", self.uid, int(self.ip), self.tariff.tid, self.is_active)
return dt return dt
def deserialize(self, data, tariff=None): def deserialize(self, data, tariff=None):
@ -110,7 +122,10 @@ class AbonStruct(BaseStruct):
return r return r
def __str__(self): def __str__(self):
return "uid=%d, ip=%s, tariff=%s" % (self.uid, self.ip, self.tariff)
return "uid=%d, ip=%s, tariff=%s" % (self.uid, self.ip, self.tariff or '<No Service>')
def __hash__(self):
return hash(int(self.ip) + hash(self.tariff)) if self.tariff is not None else 0
# Правило шейпинга в фаере, или ещё можно сказать услуга абонента на NAS # Правило шейпинга в фаере, или ещё можно сказать услуга абонента на NAS

1
bugs.txt

@ -11,3 +11,4 @@
!!! Обязательно проверить как отрабатывает на NAS удаление и изменение AbonTariff !!! Обязательно проверить как отрабатывает на NAS удаление и изменение AbonTariff
!!! Удалить всё что связано с активацией услуги !!! Удалить всё что связано с активацией услуги
!!! Убрать досрочное завершение услуги !!! Убрать досрочное завершение услуги
! Проверить дату завершения услуги

15
chatbot/locale/ru/LC_MESSAGES/django.po

@ -20,31 +20,32 @@ msgstr ""
"%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n" "%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" "%100>=11 && n%100<=14)? 2 : 3);\n"
#: chatbot/telebot.py:60
#: chatbot/telebot.py:61
msgid "Let's get acquainted, what is your name? Write your login from billing." msgid "Let's get acquainted, what is your name? Write your login from billing."
msgstr "Давай знакомиться, как тебя зовут? Напиши свой логин из биллинга." msgstr "Давай знакомиться, как тебя зовут? Напиши свой логин из биллинга."
#: chatbot/telebot.py:80
#: chatbot/telebot.py:81
msgid "I do not know the answer to this yet." msgid "I do not know the answer to this yet."
msgstr "Я пока не знаю ответа на это" msgstr "Я пока не знаю ответа на это"
#: chatbot/telebot.py:99
#: chatbot/telebot.py:100
msgid "" msgid ""
"You are not found in the database, check that it correctly pointed out its " "You are not found in the database, check that it correctly pointed out its "
"LOGIN. Try again" "LOGIN. Try again"
msgstr "" msgstr ""
"Ты не найден в базе, проверь что правильно указал именно свой ЛОГИН. Попробуй ещё"
"Ты не найден в базе, проверь что правильно указал именно свой ЛОГИН. "
"Попробуй ещё"
#: chatbot/telebot.py:122
#: chatbot/telebot.py:123
msgid "It's not like ip address, try again" msgid "It's not like ip address, try again"
msgstr "Это не похоже на ip адрес, попробуй ещё" msgstr "Это не похоже на ip адрес, попробуй ещё"
#: chatbot/telebot.py:125
#: chatbot/telebot.py:126
#, python-format #, python-format
msgid "You're '%s', right?" msgid "You're '%s', right?"
msgstr "Ты ведь %s ?" msgstr "Ты ведь %s ?"
#: chatbot/telebot.py:135
#: chatbot/telebot.py:136
#, python-format #, python-format
msgid "Recipient '%s' does not subscribed on notifications" msgid "Recipient '%s' does not subscribed on notifications"
msgstr "%s не подписан на оповещения" msgstr "%s не подписан на оповещения"

2
chatbot/telebot.py

@ -113,7 +113,7 @@ class DjingTelebot(helper.ChatHandler):
# пингуем адрес # пингуем адрес
def ping(self, ip=None): def ping(self, ip=None):
if ip is None: if ip is None:
self._question("Let's ping, write ip. It will be necessary to wait 10 seconds", self.ping)
self._question(_("Let's ping, write ip. It will be necessary to wait 10 seconds"), self.ping)
return return
try: try:
socket.inet_aton(ip) socket.inet_aton(ip)

111
clientsideapp/locale/ru/LC_MESSAGES/django.po

@ -68,81 +68,15 @@ msgstr "Заказать услугу"
msgid "Are you sure you want to order the service?" msgid "Are you sure you want to order the service?"
msgstr "Вы уверены что хотите заказать услугу?" msgstr "Вы уверены что хотите заказать услугу?"
#: clientsideapp/templates/clientsideapp/modal_service_buy.html:10
#, python-format
msgid ""
"Your current service <a href=\"#dv\">%(current_service.title)s</a>\n"
"for the <b>%(amount)s</b> rub."
msgstr ""
"Ваша текущая услуга <a href=\"#dv\">%(current_service.title)s</a>\n"
"за <b>%(amount)s</b> руб."
#: clientsideapp/templates/clientsideapp/modal_service_buy.html:13
msgid ""
"You do not have an active service for the use of resources required service "
"purchase from the selection below.<br>\n"
"And if you have already booked then just activate the service from the list "
"ordered."
msgstr ""
"У вас нет активной услуги, для использования ресурсов приобретите нужную "
"услугу из представленных ниже.<br>\n"
"А если уже заказали то просто активируйте услугу из списка заказанных."
#: clientsideapp/templates/clientsideapp/modal_service_buy.html:21
#, python-format
msgid ""
"Inbound speed: %(speedIn)s MBit/s<br>\n"
"Outgoing speed: %(speedOut)s MBit/s<br>\n"
"Cost: %(amount)s rubles."
msgstr ""
"Входящая скорость: %(speedIn)s MBit/s<br>\n"
"Исходящая скорость: %(speedOut)s MBit/s<br>\n"
"Стоимость: %(amount)s руб."
#: clientsideapp/templates/clientsideapp/modal_service_buy.html:27
#: clientsideapp/templates/clientsideapp/modal_service_buy.html:28
msgid "Pick" msgid "Pick"
msgstr "Заказать" msgstr "Заказать"
#: clientsideapp/templates/clientsideapp/modal_service_buy.html:29
#: clientsideapp/templates/clientsideapp/modal_unsubscribe_service.html:24
#: clientsideapp/templates/clientsideapp/modal_service_buy.html:30
msgid "Close" msgid "Close"
msgstr "Закрыть" msgstr "Закрыть"
#: clientsideapp/templates/clientsideapp/modal_unsubscribe_service.html:5
msgid "Unsubscribe from service"
msgstr "Отписаться от услуги"
#: clientsideapp/templates/clientsideapp/modal_unsubscribe_service.html:8
msgid "Are you sure you want to unsubscribe from the service?"
msgstr "Вы уверены что хотите отписаться от услуги?"
#: clientsideapp/templates/clientsideapp/modal_unsubscribe_service.html:14
#, fuzzy, python-format
#| msgid ""
#| "Inbound speed: %(service.speedIn)s MBit/s<br>\n"
#| "Outgoing speed: %(service.speedOut)s MBit/s<br>\n"
#| "Cost: %(service.amount)s rubles."
msgid ""
"Inbound speed: %(service.speedIn)s MBit/s<br>Outgoing speed: "
"%(service.speedOut)s MBit/s"
msgstr ""
"Входящая скорость: %(service.speedIn)s MBit/s<br>\n"
"Исходящая скорость: %(service.speedOut)s MBit/s"
#: clientsideapp/templates/clientsideapp/modal_unsubscribe_service.html:16
msgid ""
"When you unsubscribe from the service, it just will remove it from the queue "
"inclusions your services.<br>\n"
"Your funds will not be affected, the money will not go away."
msgstr ""
"Когда вы отпишитесь от услуги, это просто уберёт её из очереди включений "
"ваших услуг.<br>\n"
"Ваши средства не буду затронуты, деньги не уйдут."
#: clientsideapp/templates/clientsideapp/modal_unsubscribe_service.html:22
msgid "Unsubscribe"
msgstr "Отписаться"
#: clientsideapp/templates/clientsideapp/pays.html:6 #: clientsideapp/templates/clientsideapp/pays.html:6
msgid "conducted payments" msgid "conducted payments"
msgstr "Проведённые платежи" msgstr "Проведённые платежи"
@ -162,3 +96,44 @@ msgstr "Комментарий"
#: clientsideapp/templates/clientsideapp/pays.html:25 #: clientsideapp/templates/clientsideapp/pays.html:25
msgid "You have not spent payments" msgid "You have not spent payments"
msgstr "У вас нет проведённых платежей" msgstr "У вас нет проведённых платежей"
#: clientsideapp/views.py:53
#, python-format
msgid "Buy the service via user side, service '%s'"
msgstr "Покупка тарифного плана через личный кабинет, тариф '%s'"
#: clientsideapp/views.py:82
#, python-format
msgid "Service '%s' has been finished"
msgstr "Услуга '%s' успешно завершена"
#: clientsideapp/views.py:87
#, python-format
msgid "Early terminated service '%s' via client side"
msgstr "Досрочное завершение услуги '%s' из личного кабинета"
#: clientsideapp/views.py:90 clientsideapp/views.py:119
#: clientsideapp/views.py:147
msgid "Act is not confirmed"
msgstr "Действие не подтверждено"
#: clientsideapp/views.py:103 clientsideapp/views.py:130
msgid "Temporary network bug"
msgstr "Временные неполадки в сети"
#: clientsideapp/views.py:126
msgid "The service was not found"
msgstr "Указанная подписка на услугу не найдена"
#: clientsideapp/views.py:145
#, python-format
msgid "The service '%s' wan successfully activated"
msgstr "Услуга '%s' успешно подключена"
#: clientsideapp/views.py:181
msgid "Are you not sure that you want buy the service?"
msgstr "Вы не уверены что хотите оплатить долг?"
#: clientsideapp/views.py:183
msgid "Your account have not enough money"
msgstr "Недостаточно средств на счету"

6
clientsideapp/templates/clientsideapp/ext.html

@ -11,7 +11,7 @@
<script src="/static/js/bootstrap.min.js"></script> <script src="/static/js/bootstrap.min.js"></script>
<script src="/static/clientside/my_clientside.js"></script> <script src="/static/clientside/my_clientside.js"></script>
<link rel="shortcut icon" href="/static/img/favicon_m.ico"> <link rel="shortcut icon" href="/static/img/favicon_m.ico">
/head>
</head>
<body> <body>
<!-- Modal --> <!-- Modal -->
<div class="modal fade" id="modFrm" tabindex="-1" role="dialog" aria-hidden="true"> <div class="modal fade" id="modFrm" tabindex="-1" role="dialog" aria-hidden="true">
@ -71,13 +71,11 @@
</li> </li>
</ul> </ul>
<span class="navbar-text">Ваш балланс <b>{{ subscriber.ballance }}</b> руб.</span> <span class="navbar-text">Ваш балланс <b>{{ subscriber.ballance }}</b> руб.</span>
</div>
<!--/.nav-collapse -->
</div><!--/.nav-collapse -->
</div> </div>
</div> </div>
<!-- Begin page content -->
<div class="container"> <div class="container">
{% if request.user.is_staff %} {% if request.user.is_staff %}

3
clientsideapp/templates/clientsideapp/index.html

@ -9,8 +9,7 @@
<li><b>Телефоны:</b> +79788328885, +79788318999</li> <li><b>Телефоны:</b> +79788328885, +79788318999</li>
<li><b>Адрес:</b> пос. Нижнегорский, ул. Победы 38. 2й этаж старого дома быта, возле рынка</li> <li><b>Адрес:</b> пос. Нижнегорский, ул. Победы 38. 2й этаж старого дома быта, возле рынка</li>
</ul> </ul>
<script type="text/javascript" charset="utf-8" async
src="https://api-maps.yandex.ru/services/constructor/1.0/js/?um=constructor%3A6db1b7465ef7d4258318e1ee84048adcb7295b814dbd27a467e9eed21b3c5c8e&amp;width=100%25&amp;height=529&amp;lang=ru_RU&amp;scroll=true"></script>
<script type="text/javascript" charset="utf-8" async src="https://api-maps.yandex.ru/services/constructor/1.0/js/?um=constructor%3A62221c38a606ad870f2cabc9fd476c8ae1a532993915b73553693f7fb5c7de68&amp;width=100%25&amp;height=654&amp;lang=ru_RU&amp;scroll=true"></script>
</div> </div>
</div> </div>

28
clientsideapp/templates/clientsideapp/modal_activate_service.html

@ -1,28 +0,0 @@
<form action="{% url 'client_side:activate_service' abtar.pk %}" method="post">{% csrf_token %}
<div class="modal-header primary">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title"><span class="glyphicon glyphicon-question-sign"></span>Активировать услугу</h4>
</div>
<div class="modal-body">
<h3>Вы уверены что хотите активировать услугу?</h3>
<input type="hidden" name="finish_confirm" value="yes">
<h3>{{ service.title }}</h3>
<p>{{ service.descr }}</p>
<!--<p>Входящая скорость: {{ service.speedIn }} MBit/s<br>
Исходящая скорость: {{ service.speedOut }} MBit/s</p>-->
<p>Вы уверены что хотите активировать эту услугу?<br>
Обратите внимание что с вашего счёта <b>снимутся деньги</b> и откроется доступ к ресурсам оплаченной
услуги.<br>
Стоимость услуги: {{ amount }}руб. На счету {{ abon.ballance }} руб, останется {{ diff }} руб.</p>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-sm btn-success">
<span class="glyphicon glyphicon-shopping-cart"></span> Активировать
</button>
<button type="button" class="btn btn-default" data-dismiss="modal">Закрыть</button>
</div>
</form>

32
clientsideapp/templates/clientsideapp/modal_complete_service.html

@ -1,32 +0,0 @@
<form action="{% url 'client_side:complete_service' abtar.pk %}" method="post">{% csrf_token %}
<div class="modal-header primary">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title"><span class="glyphicon glyphicon-question-sign"></span>Завершить услугу</h4>
</div>
<div class="modal-body">
<h3>Вы уверены что хотите завершить услугу?</h3>
<input type="hidden" name="finish_confirm" value="yes">
<h3>{{ service.title }}</h3>
<p>{{ service.descr }}</p>
<p>Входящая скорость: {{ service.speedIn }} MBit/s<br>
Исходящая скорость: {{ service.speedOut }} MBit/s</p>
<p>Завершение услуги приведёт к прекращению действия услуги. Т.е. интернет отключится.<br>
Деньги за неиспользованные ресурсы возвращены не будут.<br>
Для возобновления обслуживания вы должны купить новую услугу.</p>
Услуга была подключена: {{ abtar.time_start|date:'d F Y, H:i:s' }}<br/>
Сегодня: {% now "d F Y, H:i:s" %}<br/>
Время использования: {{ time_use }}<br/>
Полная стоимость услуги: {{ service.amount }}<br/>
<b>Итоговая стоимость: {{ abtar.calc_amount_service }}</b>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-sm btn-danger">
<span class="glyphicon glyphicon-alert"></span> Завершить
</button>
<button type="button" class="btn btn-default" data-dismiss="modal">Закрыть</button>
</div>
</form>

10
clientsideapp/templates/clientsideapp/modal_service_buy.html

@ -6,13 +6,7 @@
</div> </div>
<div class="modal-body"> <div class="modal-body">
<h3>{% trans 'Are you sure you want to order the service?' %}</h3> <h3>{% trans 'Are you sure you want to order the service?' %}</h3>
{% if current_service %}
<p>{% blocktrans with amount=current_service.amount %}Your current service <a href="#dv">{{ current_service.title }}</a>
for the <b>{{ amount }}</b> rub.{% endblocktrans %}</p>
{% else %}
<p>{% blocktrans %}You do not have an active service for the use of resources required service purchase from the selection below.<br>
And if you have already booked then just activate the service from the list ordered.{% endblocktrans %}</p>
{% endif %}
<p>Будте внимательны, после заказа услуги с вашего счёта <b>снимутся средства</b>, и вы сможете пользоваться купленной услугой.</p>
<h3>{{ service.title }}</h3> <h3>{{ service.title }}</h3>
@ -25,7 +19,7 @@ Cost: {{ amount }} rubles.{% endblocktrans %}</p>-->
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="submit" class="btn btn-sm btn-primary"> <button type="submit" class="btn btn-sm btn-primary">
<span class="glyphicon glyphicon-save"></span> {% trans 'Pick' %}
<span class="glyphicon glyphicon-shopping-cart"></span> {% trans 'Pick' %}
</button> </button>
<button type="button" class="btn btn-default" data-dismiss="modal">{% trans 'Close' %}</button> <button type="button" class="btn btn-default" data-dismiss="modal">{% trans 'Close' %}</button>
</div> </div>

26
clientsideapp/templates/clientsideapp/modal_unsubscribe_service.html

@ -1,26 +0,0 @@
{% load i18n %}
<form action="{% url 'client_side:unsubscribe_service' abtar.pk %}" method="post">{% csrf_token %}
<div class="modal-header primary">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title"><span class="glyphicon glyphicon-question-sign"></span>{% trans 'Unsubscribe from service' %}</h4>
</div>
<div class="modal-body">
<h3>{% trans 'Are you sure you want to unsubscribe from the service?' %}</h3>
<input type="hidden" name="finish_confirm" value="yes">
<h3>{{ service.title }}</h3>
<p>{{ service.descr }}</p>
<p>{% blocktrans %}Inbound speed: {{ service.speedIn }} MBit/s<br>Outgoing speed: {{ service.speedOut }} MBit/s{% endblocktrans %}</p>
<p>{% blocktrans %}When you unsubscribe from the service, it just will remove it from the queue inclusions your services.<br>
Your funds will not be affected, the money will not go away.{% endblocktrans %}</p>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-sm btn-primary">
<span class="glyphicon glyphicon-alert"></span> {% trans 'Unsubscribe' %}
</button>
<button type="button" class="btn btn-default" data-dismiss="modal">{% trans 'Close' %}</button>
</div>
</form>

94
clientsideapp/templates/clientsideapp/services.html

@ -1,57 +1,45 @@
{% extends 'clientsideapp/ext.html' %} {% extends 'clientsideapp/ext.html' %}
{% load i18n %}
{% block client_main %} {% block client_main %}
<div class="page-header"> <div class="page-header">
<h3>Управление услугами</h3>
{% if current_service %}
<p>Ваша текущая услуга <b>{{ current_service.tariff.title }}</b>
за <b>{{ current_service.tariff.amount }}</b> руб.</p>
{% else %}
<p>У вас нет активной услуги, для использования ресурсов приобретите нужную услугу из представленных
ниже.<br>
А если уже заказали, то просто активируйте услугу из списка заказанных.</p>
{% endif %}
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-lg-4">
<h4>Ваши текущие услуги</h4>
<ul class="list-group">
{% for abon_tariff in own_abon_tariffs %}
<li class="list-group-item">
<div class="btn-group">
{% if abon_tariff == current_service %}
<a href="{% url 'client_side:complete_service' abon_tariff.pk %}"
class="btn btn-xs btn-danger btn-modal" title="Завершить услугу досрочно">
<span class="glyphicon glyphicon-remove"></span>
</a>
{% else %}
{% if not current_service %}
<a href="{% url 'client_side:activate_service' abon_tariff.pk %}"
class="btn btn-xs btn-success btn-modal" title="Активировать">
<span class="glyphicon glyphicon-fire"></span>
</a>
{% endif %}
<a href="{% url 'client_side:unsubscribe_service' abon_tariff.pk %}"
class="btn btn-xs btn-danger btn-modal" title="Удалить услугу из списка">
<span class="glyphicon glyphicon-remove"></span>
</a>
{% endif %}
<div class="col-lg-5">
{% if current_service %}
<div class="panel panel-default">
<div class="panel-heading">
Ваша текущая услуга
</div>
<div class="panel-body">
<h3 class="panel-title">{{ current_service.tariff.title }}</h3><br>
<dl class="dl-horizontal">
<dt>Дата подключения</dt>
<dd>{{ current_service.time_start|date:"d E Y, l" }}</dd>
<dt>Время завершения услуги</dt>
<dd>{{ current_service.deadline|date:"d E Y, l" }}</dd>
<dt>Стоимость</dt>
<dd>{{ current_service.tariff.amount }} {% trans 'currency' %}</dd>
</dl>
<p>{{ current_service.tariff.descr }}</p>
</div>
</div> </div>
<span class="badge">{{ abon_tariff.tariff.amount }}</span>
{% if abon_tariff.is_started %}
<b>{{ abon_tariff.tariff.title }}</b>
{% else %} {% else %}
{{ abon_tariff.tariff.title }}
<div class="alert alert-warning" role="alert">
<span class="glyphicon glyphicon-exclamation-sign"></span>
<strong>Внимание!</strong> У вас нет услуги, для использования ресурсов приобретите нужную услугу из представленных тут.
</div>
{% endif %} {% endif %}
</li>
{% empty %}
<li class="list-group-item">У вас нет заказанных услуг</li>
{% endfor %}
</ul>
</div> </div>
<div class="col-lg-8">
<div class="col-lg-7">
<div class="panel panel-default">
<div class="panel-heading">
Доступные для заказа услуги
</div>
<div class="panel-body">
<div class="container-fluid"> <div class="container-fluid">
<div class="row"> <div class="row">
{% for tarif in tarifs %} {% for tarif in tarifs %}
@ -61,12 +49,12 @@
<i>{{ tarif.amount }} руб.</i> <i>{{ tarif.amount }} руб.</i>
<p>{{ tarif.descr }}</p> <p>{{ tarif.descr }}</p>
{% if request.user.is_staff %}
{% if request.user.is_staff or current_service %}
<a href="#" class="btn btn-primary disabled"> <a href="#" class="btn btn-primary disabled">
{% else %} {% else %}
<a href="{% url 'client_side:buy_service' tarif.id %}" class="btn btn-primary btn-modal"> <a href="{% url 'client_side:buy_service' tarif.id %}" class="btn btn-primary btn-modal">
{% endif %} {% endif %}
<span class="glyphicon glyphicon-shopping-cart"></span> Заказать
<span class="glyphicon glyphicon-shopping-cart"></span> {% trans 'Pick' %}
</a> </a>
</div> </div>
{% empty %} {% empty %}
@ -78,6 +66,9 @@
</div> </div>
</div> </div>
</div> </div>
</div>
</div>
</div> </div>
</div> </div>
@ -89,14 +80,11 @@
Как работает личный кабинет, раздел услуг Как работает личный кабинет, раздел услуг
</div> </div>
<div class="panel-body"> <div class="panel-body">
<p>Перед вами список ваших текущих (подключённых) услуг под надписью "<b>Ваши текущие услуги</b>", и список доступных для подключения услуг справа.
Когда у вас нет текущих услуг, то внизу в рамочке вы видите надпись "<b>У вас нет заказанных услуг</b>". Чтобы заказать новую услугу, вам надо нажать на кнопку "<b>Заказать</b>" справа
в разделе доступных услуг. Когда вы заказываете услугу, то видите диалог в котором вам поясняется какую услугу вы подключаете и сколько денег спишется с вашего счёта.</p>
<p>Когда у вас уже есть подключённая услуга и вы заказываете новую услугу, то она встаёт в очередь подключений, деньги в этот момент не снимаются. По истечению текущей услуги в конце месяца вам автоматически подключится услуга
из очереди. Услуга не может быть подключена если на счету не достаточно средств.</p>
<p>Когда вы хотите досрочно завершить услугу, то можете нажать на красный крестик восле вашей активной услуги, в этот момент вам показывается диалог, в котором указываются подробности операции. Вы должны подтвердить действие
нажатием кнопки <b>Завершить</b>. Внимательно прочитайте предупреждение в диалоге, так как там вы увидите что будет с вашими средствами. Если вы нажимаете красный крестик возле услуги которая ещё не стала активной, то она просто
уберётся из очереди на подключение, и ваши средства никак не затронутся.</p>
<p>Перед вами находится 2 столбца, в левом ваша текущая подключённая услуга, а в правом доступные для заказа услуги.
Когда у вас уже есть подключённая услуга то заказать новую вы не можете пока не завершится текущая.</p>
<p>Когда у вас нет действующей подключённой услуги то кнопка заказа станет активной, и вы сможете заказать для себя услугу.
Обратите внимание что именно в этот момент с вашего счёта снимутся деньги в соответствии со стоимостью услуги.
</p>
</div> </div>
</div> </div>
</div> </div>

3
clientsideapp/urls.py

@ -8,9 +8,6 @@ urlpatterns = [
url(r'^pays$', views.pays, name='pays'), url(r'^pays$', views.pays, name='pays'),
url(r'^services$', views.services, name='services'), url(r'^services$', views.services, name='services'),
url(r'^services/(?P<srv_id>\d+)/buy$', views.buy_service, name='buy_service'), url(r'^services/(?P<srv_id>\d+)/buy$', views.buy_service, name='buy_service'),
url(r'^services/(?P<srv_id>\d+)/finish$', views.complete_service, name='complete_service'),
url(r'^services/(?P<srv_id>\d+)/unsubscribe$', views.unsubscribe_service, name='unsubscribe_service'),
url(r'^services/(?P<srv_id>\d+)/activate$', views.activate_service, name='activate_service'),
url(r'^debts$', views.debts_list, name='debts'), url(r'^debts$', views.debts_list, name='debts'),
url(r'^debts/(?P<d_id>\d+)$', views.debt_buy, name='debt_buy') url(r'^debts/(?P<d_id>\d+)$', views.debt_buy, name='debt_buy')
] ]

114
clientsideapp/views.py

@ -3,11 +3,12 @@ from django.contrib.auth.decorators import login_required
from django.contrib.gis.shortcuts import render_to_text from django.contrib.gis.shortcuts import render_to_text
from django.shortcuts import render, get_object_or_404, redirect from django.shortcuts import render, get_object_or_404, redirect
from django.contrib import messages from django.contrib import messages
from django.utils import timezone
from django.db.transaction import atomic
from django.utils.translation import ugettext_lazy as _
from abonapp.models import AbonLog, AbonTariff, InvoiceForPayment, Abon from abonapp.models import AbonLog, AbonTariff, InvoiceForPayment, Abon
from tariff_app.models import Tariff from tariff_app.models import Tariff
from mydefs import pag_mn, RuTimedelta, LogicError
from mydefs import pag_mn, LogicError
from agent import NasFailedResult, NasNetworkError from agent import NasFailedResult, NasNetworkError
@ -29,28 +30,24 @@ def pays(request):
def services(request): def services(request):
abon = Abon.objects.get(pk=request.user.pk) abon = Abon.objects.get(pk=request.user.pk)
all_tarifs = abon.group.tariffs.filter(is_admin=False) all_tarifs = abon.group.tariffs.filter(is_admin=False)
own_abon_tariffs = AbonTariff.objects.filter(abon=abon)
current_service = own_abon_tariffs.exclude(time_start=None)
current_service = current_service[0] if current_service.count() > 0 else None
return render(request, 'clientsideapp/services.html', { return render(request, 'clientsideapp/services.html', {
'tarifs': all_tarifs, 'tarifs': all_tarifs,
'own_abon_tariffs': own_abon_tariffs,
'current_service': current_service
'current_service': abon.active_tariff()
}) })
@login_required @login_required
@atomic
def buy_service(request, srv_id): def buy_service(request, srv_id):
abon = get_object_or_404(Abon, pk=request.user.pk) abon = get_object_or_404(Abon, pk=request.user.pk)
service = get_object_or_404(Tariff, pk=srv_id) service = get_object_or_404(Tariff, pk=srv_id)
try: try:
current_service = abon.active_tariff() current_service = abon.active_tariff()
if request.method == 'POST': if request.method == 'POST':
abon.pick_tariff(service, request.user, 'Покупка тарифного плана через личный кабинет, тариф "%s"'
abon.pick_tariff(service, request.user, _("Buy the service via user side, service '%s'")
% service) % service)
messages.success(request, 'Вы подписались на новую услугу. Она встала на очередь подключений. '
'Когда закончится ваша текущая услуга, то включится эта')
messages.success(request, _("The service '%s' wan successfully activated") % service.title)
else: else:
return render_to_text('clientsideapp/modal_service_buy.html', { return render_to_text('clientsideapp/modal_service_buy.html', {
'service': service, 'service': service,
@ -63,98 +60,6 @@ def buy_service(request, srv_id):
return redirect('client_side:services') return redirect('client_side:services')
@login_required
def complete_service(request, srv_id):
abtar = get_object_or_404(AbonTariff, id=srv_id)
service = abtar.tariff
try:
if request.method == 'POST':
# досрочно завершаем услугу
finish_confirm = request.POST.get('finish_confirm')
if finish_confirm == 'yes':
# удаляем запись о текущей услуге.
abtar.delete()
messages.success(request, 'Услуга "%s" успешно завершена' % service.title)
AbonLog.objects.create(
abon=abtar.abon,
amount=0.0,
author=abtar.abon,
comment='Досрочное завершение услуги "%s" из личного кабинета' % service.title
)
else:
messages.error(request, 'Действие не подтверждено')
else:
time_use = RuTimedelta(timezone.now() - abtar.time_start)
return render_to_text('clientsideapp/modal_complete_service.html', {
'service': service,
'abtar': abtar,
'time_use': time_use
}, request=request)
except LogicError as e:
messages.error(request, e)
except NasFailedResult as e:
messages.error(request, e)
except NasNetworkError:
messages.error(request, 'Временные неполадки')
return redirect('client_side:services')
@login_required
def unsubscribe_service(request, srv_id):
abtar = get_object_or_404(AbonTariff, id=srv_id)
service = abtar.tariff
try:
if request.method == 'POST':
# досрочно завершаем услугу
if request.POST.get('finish_confirm') == 'yes':
AbonTariff.objects.get(pk=srv_id).delete()
messages.success(request, 'Вы успешно отписались от услуги, "%s"' % service.title)
else:
messages.error(request, 'Действие не подтверждено')
else:
return render_to_text('clientsideapp/modal_unsubscribe_service.html', {
'abtar': abtar,
'service': service
}, request=request)
except AbonTariff.DoesNotExist:
messages.error(request, 'Указанная подписка на услугу не найдена')
except NasFailedResult as e:
messages.error(request, e)
except NasNetworkError:
messages.error(request, 'Временные неполадки')
return redirect('client_side:services')
@login_required
def activate_service(request, srv_id):
abtar = get_object_or_404(AbonTariff, id=srv_id)
service = abtar.tariff
amount = abtar.calc_amount_service()
try:
if request.method == 'POST':
# активируем услугу
if request.POST.get('finish_confirm') == 'yes':
abtar.activate(request.user)
messages.success(request, 'Услуга "%s" успешно активирована' % service.title)
else:
messages.error(request, 'Запрос не подтверждён')
return redirect('client_side:services')
except NasFailedResult as e:
messages.error(request, e)
except NasNetworkError as e:
messages.warning(request, e)
except LogicError as e:
messages.error(request, e)
return render_to_text('clientsideapp/modal_activate_service.html', {
'abtar': abtar,
'service': service,
'amount': amount,
'abon': abtar.abon,
'diff': abtar.abon.ballance - amount
}, request=request)
@login_required @login_required
def debts_list(request): def debts_list(request):
debts = InvoiceForPayment.objects.filter(abon=request.user) debts = InvoiceForPayment.objects.filter(abon=request.user)
@ -164,6 +69,7 @@ def debts_list(request):
@login_required @login_required
@atomic
def debt_buy(request, d_id): def debt_buy(request, d_id):
debt = get_object_or_404(InvoiceForPayment, id=d_id) debt = get_object_or_404(InvoiceForPayment, id=d_id)
abon = get_object_or_404(Abon, id=request.user.id) abon = get_object_or_404(Abon, id=request.user.id)
@ -171,9 +77,9 @@ def debt_buy(request, d_id):
try: try:
sure = request.POST.get('sure') sure = request.POST.get('sure')
if sure != 'on': if sure != 'on':
raise LogicError('Вы не уверены что хотите оплатить долг?')
raise LogicError(_("Are you not sure that you want buy the service?"))
if abon.ballance < debt.amount: if abon.ballance < debt.amount:
raise LogicError('Не достаточно средств на счету')
raise LogicError(_('Your account have not enough money'))
abon.make_pay(request.user, debt.amount) abon.make_pay(request.user, debt.amount)
debt.set_ok() debt.set_ok()

1
devapp/admin.py

@ -5,4 +5,3 @@ from . import models
admin.site.register(models.Device) admin.site.register(models.Device)
admin.site.register(models.Port) admin.site.register(models.Port)
admin.site.register(models.PortStates)

10
devapp/base_intr.py

@ -28,6 +28,16 @@ class DevBase(object, metaclass=ABCMeta):
def get_template_name(self): def get_template_name(self):
"""Получаем путь к html шаблону отображения устройства""" """Получаем путь к html шаблону отображения устройства"""
@staticmethod
@abstractmethod
def has_attachable_to_subscriber():
"""Можно-ли подключать устройство к абоненту"""
@staticmethod
@abstractmethod
def is_use_device_port():
"""True если при авторизации по opt82 используется порт"""
class BasePort(object, metaclass=ABCMeta): class BasePort(object, metaclass=ABCMeta):
def __init__(self, num, name, status, mac, speed): def __init__(self, num, name, status, mac, speed):

158
devapp/dev_types.py

@ -5,22 +5,6 @@ from datetime import timedelta
from .base_intr import DevBase, SNMPBaseWorker, BasePort from .base_intr import DevBase, SNMPBaseWorker, BasePort
oids = {
'reboot': '.1.3.6.1.4.1.2021.8.1.101.1',
'get_ports': {
'names': '.1.3.6.1.4.1.171.10.134.2.1.1.100.2.1.3',
'stats': '.1.3.6.1.2.1.2.2.1.7',
'macs': '.1.3.6.1.2.1.2.2.1.6',
'speeds': '.1.3.6.1.2.1.31.1.1.1.15'
},
'name': '.1.3.6.1.2.1.1.1.0',
'position': '.1.3.6.1.2.1.1.5.0',
'toggle_port': '.1.3.6.1.2.1.2.2.1.7',
'uptime': '.1.3.6.1.2.1.1.8.0'
}
class DLinkPort(BasePort): class DLinkPort(BasePort):
def __init__(self, num, name, status, mac, speed, snmpWorker): def __init__(self, num, name, status, mac, speed, snmpWorker):
@ -31,15 +15,13 @@ class DLinkPort(BasePort):
# выключаем этот порт # выключаем этот порт
def disable(self): def disable(self):
self.snmp_worker.set_int_value( self.snmp_worker.set_int_value(
"%s.%d" % (oids['toggle_port'], self.num),
2
"%s.%d" % ('.1.3.6.1.2.1.2.2.1.7', self.num), 2
) )
# включаем этот порт # включаем этот порт
def enable(self): def enable(self):
self.snmp_worker.set_int_value( self.snmp_worker.set_int_value(
"%s.%d" % (oids['toggle_port'], self.num),
1
"%s.%d" % ('.1.3.6.1.2.1.2.2.1.7', self.num), 1
) )
@ -54,15 +36,15 @@ class DLinkDevice(DevBase, SNMPBaseWorker):
return _('DLink switch') return _('DLink switch')
def reboot(self): def reboot(self):
pass
return self.get_item('.1.3.6.1.4.1.2021.8.1.101.1')
def get_ports(self): def get_ports(self):
nams = self.get_list(oids['get_ports']['names'])
stats = self.get_list(oids['get_ports']['stats'])
macs = self.get_list(oids['get_ports']['macs'])
speeds = self.get_list(oids['get_ports']['speeds'])
nams = self.get_list('.1.3.6.1.4.1.171.10.134.2.1.1.100.2.1.3')
stats = self.get_list('.1.3.6.1.2.1.2.2.1.7')
macs = self.get_list('.1.3.6.1.2.1.2.2.1.6')
speeds = self.get_list('.1.3.6.1.2.1.31.1.1.1.15')
res = [] res = []
ln = len(macs)
ln = len(speeds)
for n in range(ln): for n in range(ln):
status = True if int(stats[n]) == 1 else False status = True if int(stats[n]) == 1 else False
res.append(DLinkPort( res.append(DLinkPort(
@ -75,21 +57,29 @@ class DLinkDevice(DevBase, SNMPBaseWorker):
return res return res
def get_device_name(self): def get_device_name(self):
return self.get_item(oids['name'])
return self.get_item('.1.3.6.1.2.1.1.1.0')
def uptime(self): def uptime(self):
uptimestamp = safe_int(self.get_item(oids['uptime']))
uptimestamp = safe_int(self.get_item('.1.3.6.1.2.1.1.8.0'))
tm = RuTimedelta(timedelta(seconds=uptimestamp/100)) or RuTimedelta(timedelta()) tm = RuTimedelta(timedelta(seconds=uptimestamp/100)) or RuTimedelta(timedelta())
return tm return tm
def get_template_name(self): def get_template_name(self):
return 'devapp/ports.html'
return 'ports.html'
@staticmethod
def has_attachable_to_subscriber():
return True
@staticmethod
def is_use_device_port():
return True
class ONUdev(BasePort): class ONUdev(BasePort):
def __init__(self, num, name, status, mac, speed, signal, snmpWorker): def __init__(self, num, name, status, mac, speed, signal, snmpWorker):
BasePort.__init__(self, num, name, status, mac, speed)
assert issubclass(snmpWorker.__class__ , SNMPBaseWorker)
super(ONUdev, self).__init__(num, name, status, mac, speed)
assert issubclass(snmpWorker.__class__, SNMPBaseWorker)
self.snmp_worker = snmpWorker self.snmp_worker = snmpWorker
self.signal = signal self.signal = signal
@ -113,7 +103,7 @@ class OLTDevice(DevBase, SNMPBaseWorker):
@staticmethod @staticmethod
def description(): def description():
return _('PON ONU')
return _('PON OLT')
def reboot(self): def reboot(self):
pass pass
@ -146,10 +136,104 @@ class OLTDevice(DevBase, SNMPBaseWorker):
return tm return tm
def get_template_name(self): def get_template_name(self):
return 'devapp/olt.html'
return 'olt.html'
@staticmethod
def has_attachable_to_subscriber():
return False
@staticmethod
def is_use_device_port():
return False
class OnuDevice(DevBase, SNMPBaseWorker):
@staticmethod
def description():
return _('PON ONU')
def reboot(self):
pass
def get_ports(self):
pass
def get_device_name(self):
pass
def uptime(self):
pass
def get_template_name(self):
return "onu.html"
@staticmethod
def has_attachable_to_subscriber():
return True
@staticmethod
def is_use_device_port():
return False
class EltexPort(BasePort):
def __init__(self, num, name, status, mac, speed, snmpWorker):
BasePort.__init__(self, num, name, status, mac, speed)
assert issubclass(snmpWorker.__class__ , SNMPBaseWorker)
self.snmp_worker = snmpWorker
# выключаем этот порт
def disable(self):
self.snmp_worker.set_int_value(
"%s.%d" % ('.1.3.6.1.2.1.2.2.1.7', self.num),
2
)
# включаем этот порт
def enable(self):
self.snmp_worker.set_int_value(
"%s.%d" % ('.1.3.6.1.2.1.2.2.1.7', self.num),
1
)
DEVICE_TYPES = (
('Dl', DLinkDevice),
('Pn', OLTDevice)
)
class EltexSwitch(DLinkDevice):
@staticmethod
def description():
return _('Eltex switch')
def get_ports(self):
#nams = self.get_list('.1.3.6.1.4.1.171.10.134.2.1.1.100.2.1.3')
stats = self.get_list('.1.3.6.1.2.1.2.2.1.7')
oper_stats = self.get_list('.1.3.6.1.2.1.2.2.1.8')
#macs = self.get_list('.1.3.6.1.2.1.2.2.1.6')
speeds = self.get_list('.1.3.6.1.2.1.31.1.1.1.15')
res = []
for n in range(28):
res.append(EltexPort(
n+1,
'',#nams[n] if len(nams) > 0 else _('does not fetch the name'),
True if int(stats[n]) == 1 else False,
'',#macs[n] if len(macs) > 0 else _('does not fetch the mac'),
int(speeds[n]) if len(speeds) > 0 and int(oper_stats[n]) == 1 else 0,
self))
return res
def get_device_name(self):
return self.get_item('.1.3.6.1.2.1.1.5.0')
def uptime(self):
uptimestamp = safe_int(self.get_item('.1.3.6.1.2.1.1.3.0'))
tm = RuTimedelta(timedelta(seconds=uptimestamp/100)) or RuTimedelta(timedelta())
return tm
@staticmethod
def has_attachable_to_subscriber():
return False
@staticmethod
def is_use_device_port():
return False

42
devapp/forms.py

@ -1,11 +1,23 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from django import forms from django import forms
from django.utils.translation import ugettext as _
from django.db import IntegrityError
from . import models from . import models
from mydefs import ip_addr_regex from mydefs import ip_addr_regex
from djing import MAC_ADDR_REGEX
class DeviceForm(forms.ModelForm): class DeviceForm(forms.ModelForm):
mac_addr = forms.CharField(widget=forms.TextInput(attrs={
'pattern': MAC_ADDR_REGEX,
'required': True,
'class': 'form-control'
}), error_messages={
'required': _('Mac address is required for fill'),
'unique': _('Device with that mac is already exist')
})
class Meta: class Meta:
model = models.Device model = models.Device
fields = '__all__' fields = '__all__'
@ -13,10 +25,9 @@ class DeviceForm(forms.ModelForm):
'ip_address': forms.TextInput(attrs={ 'ip_address': forms.TextInput(attrs={
'pattern': ip_addr_regex, 'pattern': ip_addr_regex,
'placeholder': '192.168.0.100', 'placeholder': '192.168.0.100',
'required': True,
'class': 'form-control' 'class': 'form-control'
}), }),
'comment': forms.Textarea(attrs={
'comment': forms.TextInput(attrs={
'required': True, 'required': True,
'class': 'form-control' 'class': 'form-control'
}), }),
@ -31,5 +42,32 @@ class DeviceForm(forms.ModelForm):
}), }),
'user_group': forms.Select(attrs={ 'user_group': forms.Select(attrs={
'class': 'form-control' 'class': 'form-control'
}),
'parent_dev': forms.Select(attrs={
'class': 'form-control'
})
}
class PortForm(forms.ModelForm):
class Meta:
model = models.Port
exclude = ['device']
widgets = {
'num': forms.NumberInput(attrs={
'class': 'form-control',
'min': '0'
}),
'descr': forms.TextInput(attrs={
'class': 'form-control'
}) })
} }
def save(self, commit=True):
try:
super(PortForm, self).save(commit)
except IntegrityError as e:
if "Duplicate entry" in str(e):
raise models.DeviceDBException(_('Port number on device must be unique'))
else:
raise models.DeviceDBException(e)

114
devapp/locale/ru/LC_MESSAGES/django.po

@ -1,4 +1,3 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE"S COPYRIGHT HOLDER # Copyright (C) YEAR THE PACKAGE"S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package. # This file is distributed under the same license as the PACKAGE package.
# Dmitry Novikov nerosketch@gmail.com, 2017. # Dmitry Novikov nerosketch@gmail.com, 2017.
@ -32,8 +31,8 @@ msgid "does not fetch the mac"
msgstr "не нашёл мак" msgstr "не нашёл мак"
#: devapp/dev_types.py:116 #: devapp/dev_types.py:116
msgid "PON ONU"
msgstr "ONU Голова"
msgid "PON OLT"
msgstr "PON OLT голова"
#: devapp/templates/devapp/add_dev.html:7 #: devapp/templates/devapp/add_dev.html:7
#: devapp/templates/devapp/devices.html:7 #: devapp/templates/devapp/devices.html:7
@ -41,14 +40,14 @@ msgstr "ONU Голова"
#: devapp/templates/devapp/group_list.html:7 #: devapp/templates/devapp/group_list.html:7
#: devapp/templates/devapp/group_list.html:10 #: devapp/templates/devapp/group_list.html:10
msgid "Groups" msgid "Groups"
msgstr ""
msgstr "Группы"
#: devapp/templates/devapp/add_dev.html:8 #: devapp/templates/devapp/add_dev.html:8
msgid "Add new device" msgid "Add new device"
msgstr ""
msgstr "Добавить устройство"
#: devapp/templates/devapp/add_dev.html:14 #: devapp/templates/devapp/add_dev.html:14
#: devapp/templates/devapp/ports.html:143
#: devapp/templates/devapp/ports.html:93
msgid "Not assigned" msgid "Not assigned"
msgstr "&lt;Не назначено&gt;" msgstr "&lt;Не назначено&gt;"
@ -84,7 +83,7 @@ msgstr "Точка топологии"
#: devapp/templates/devapp/add_dev.html:71 devapp/templates/devapp/dev.html:59 #: devapp/templates/devapp/add_dev.html:71 devapp/templates/devapp/dev.html:59
msgid "User group" msgid "User group"
msgstr ""
msgstr "Группа"
#: devapp/templates/devapp/add_dev.html:81 devapp/templates/devapp/dev.html:69 #: devapp/templates/devapp/add_dev.html:81 devapp/templates/devapp/dev.html:69
msgid "Save" msgid "Save"
@ -118,7 +117,7 @@ msgstr "Устройства без группы"
#: devapp/templates/devapp/group_list.html:18 #: devapp/templates/devapp/group_list.html:18
msgid "Group title" msgid "Group title"
msgstr ""
msgstr "Название"
#: devapp/templates/devapp/group_list.html:28 #: devapp/templates/devapp/group_list.html:28
#, fuzzy #, fuzzy
@ -128,12 +127,15 @@ msgstr "Эта точка не пингуется"
#: devapp/templates/devapp/group_list.html:37 #: devapp/templates/devapp/group_list.html:37
msgid "Add group" msgid "Add group"
msgstr ""
msgstr "Добавить группу"
#: devapp/templates/devapp/olt.html:13 #: devapp/templates/devapp/olt.html:13
msgid "Mac" msgid "Mac"
msgstr "Мак" msgstr "Мак"
msgid "Mac address"
msgstr "Мак адрес"
#: devapp/templates/devapp/olt.html:14 #: devapp/templates/devapp/olt.html:14
msgid "Name" msgid "Name"
msgstr "Имя" msgstr "Имя"
@ -148,7 +150,7 @@ msgstr "Ур. сигнала"
#: devapp/templates/devapp/olt.html:34 #: devapp/templates/devapp/olt.html:34
msgid "Ports not found" msgid "Ports not found"
msgstr "Онушки не получил"
msgstr "Порты не найдены"
#: devapp/templates/devapp/ports.html:9 #: devapp/templates/devapp/ports.html:9
msgid "Title of the type of switch" msgid "Title of the type of switch"
@ -158,39 +160,50 @@ msgstr "Название типа свича"
msgid "Uptime" msgid "Uptime"
msgstr "Без перезагрузки" msgstr "Без перезагрузки"
#: devapp/templates/devapp/ports.html:42
#: devapp/templates/devapp/ports.html:33
msgid "Disable port"
msgstr "Выключить порт"
#: devapp/templates/devapp/ports.html:37
msgid "Enable port"
msgstr "Включить порт"
#: devapp/templates/devapp/ports.html:43
msgid "We have not received info, please check options :(" msgid "We have not received info, please check options :("
msgstr "Инфа не получена, проверьте настройки :(" msgstr "Инфа не получена, проверьте настройки :("
#: devapp/templates/devapp/ports.html:54
#: devapp/templates/devapp/ports.html:55
msgid "Device log" msgid "Device log"
msgstr "Лог устройства" msgstr "Лог устройства"
#: devapp/templates/devapp/ports.html:61
#: devapp/templates/devapp/ports.html:62
msgid "Level" msgid "Level"
msgstr "Уровень" msgstr "Уровень"
#: devapp/templates/devapp/ports.html:62
#: devapp/templates/devapp/ports.html:63
msgid "Description" msgid "Description"
msgstr "Описание" msgstr "Описание"
#: devapp/templates/devapp/ports.html:63
#: devapp/templates/devapp/ports.html:64
msgid "Date" msgid "Date"
msgstr "Дата" msgstr "Дата"
#: devapp/templates/devapp/ports.html:129
#: devapp/templates/devapp/ports.html:79
msgid "Ports comment" msgid "Ports comment"
msgstr "Комментарии портов" msgstr "Комментарии портов"
#: devapp/templates/devapp/ports.html:135
#: devapp/templates/devapp/ports.html:85
msgid "Port" msgid "Port"
msgstr "Порт" msgstr "Порт"
#: devapp/templates/devapp/ports.html:136
msgid "Ports"
msgstr "Порты"
#: devapp/templates/devapp/ports.html:86
msgid "Title" msgid "Title"
msgstr "Название" msgstr "Название"
#: devapp/templates/devapp/ports.html:147
#: devapp/templates/devapp/ports.html:97
msgid "We have not received info for ports" msgid "We have not received info for ports"
msgstr "Инфа о портах не получена" msgstr "Инфа о портах не получена"
@ -224,3 +237,66 @@ msgstr "Ошибка SNMP на устройстве"
msgid "Edit" msgid "Edit"
msgstr "Редактировать" msgstr "Редактировать"
msgid "Device does not exist"
msgstr "Устойство не найдено"
msgid "Number"
msgstr "Номер"
msgid "Mode"
msgstr "Режим"
msgid "Add"
msgstr "Добавить"
msgid "Add ports"
msgstr "Добавить порты"
msgid "Device is not have a group, please fix that"
msgstr "У устройства нет группы, пожалуйста, исправьте это"
msgid "Delete"
msgstr "Удалить"
msgid "Port does not exist"
msgstr "Порт не найден"
msgid "Port successfully removed"
msgstr "Порт успешно удалён"
msgid "PON ONU"
msgstr "Онушка"
msgid "Are you sure that you want to delete switch port from db?"
msgstr "Вы уверены что хотите удалить порт свича из бд?"
msgid "Port successfully saved"
msgstr "Порт успешно сохранён"
msgid "Port number on device must be unique"
msgstr "Номер порта на устройстве должен быть уникальным"
msgid "Mac address is required for fill"
msgstr "MAC-адрес необходим для заполнения"
msgid "Device with that mac is already exist"
msgstr "Устройство с этим мак-адресом уже есть"
msgid "Parent device"
msgstr "Родительское устройство"
msgid "Attached user"
msgstr "Прикрепленный абонент"
msgid "Find the device"
msgstr "Найти устройство"
msgid "Find the subscriber"
msgstr "Найти абонента"
msgid "View the device"
msgstr "Посмотреть устройство"
msgid "Eltex switch"
msgstr "Элтекс свич"

2
devapp/migrations/0005_auto_20170502_2232.py

@ -15,6 +15,6 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='device', model_name='device',
name='devtype', name='devtype',
field=models.CharField(choices=[('Dl', 'DLink switch'), ('Pn', 'PON ONU')], default='Dl', max_length=2),
field=models.CharField(choices=[('Dl', 'DLink switch'), ('Pn', 'PON OLT')], default='Dl', max_length=2),
), ),
] ]

48
devapp/migrations/0006_auto_20170705_1403.py

@ -0,0 +1,48 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2017-07-05 14:03
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
import djing.fields
class Migration(migrations.Migration):
dependencies = [
('devapp', '0005_auto_20170502_2232'),
]
operations = [
migrations.RemoveField(
model_name='portstates',
name='port',
),
migrations.RemoveField(
model_name='port',
name='speed',
),
migrations.AddField(
model_name='device',
name='mac_addr',
field=djing.fields.MACAddressField(blank=True, integer=True, null=True, unique=True),
),
migrations.AddField(
model_name='device',
name='parent_dev',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='devapp.Device'),
),
migrations.AddField(
model_name='port',
name='descr',
field=models.CharField(blank=True, max_length=60, null=True),
),
migrations.AlterField(
model_name='device',
name='devtype',
field=models.CharField(choices=[('Dl', 'DLink switch'), ('Pn', 'PON OLT'), ('On', 'PON ONU')], default='Dl', max_length=2),
),
migrations.DeleteModel(
name='PortStates',
),
]

20
devapp/migrations/0007_auto_20170816_1109.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2017-08-16 11:09
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('devapp', '0006_auto_20170705_1403'),
]
operations = [
migrations.AlterField(
model_name='device',
name='devtype',
field=models.CharField(choices=[('Dl', 'DLink switch'), ('Pn', 'PON OLT'), ('On', 'PON ONU'), ('Ex', 'Eltex switch')], default='Dl', max_length=2),
),
]

74
devapp/models.py

@ -1,24 +1,36 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from django.db import models from django.db import models
from djing.fields import MACAddressField
from .base_intr import DevBase from .base_intr import DevBase
from mydefs import MyGenericIPAddressField, MyChoicesAdapter from mydefs import MyGenericIPAddressField, MyChoicesAdapter
from .dev_types import DEVICE_TYPES
from . import dev_types
from mapapp.models import Dot from mapapp.models import Dot
from subprocess import call
from django.conf import settings
class _DeviceChoicesAdapter(MyChoicesAdapter):
def __init__(self):
super().__init__(DEVICE_TYPES)
DEVICE_TYPES = (
('Dl', dev_types.DLinkDevice),
('Pn', dev_types.OLTDevice),
('On', dev_types.OnuDevice),
('Ex', dev_types.EltexSwitch)
)
class DeviceDBException(Exception):
pass
class Device(models.Model): class Device(models.Model):
ip_address = MyGenericIPAddressField() ip_address = MyGenericIPAddressField()
mac_addr = MACAddressField(null=True, blank=True, unique=True)
comment = models.CharField(max_length=256) comment = models.CharField(max_length=256)
devtype = models.CharField(max_length=2, default=DEVICE_TYPES[0][0], choices=_DeviceChoicesAdapter())
devtype = models.CharField(max_length=2, default=DEVICE_TYPES[0][0], choices=MyChoicesAdapter(DEVICE_TYPES))
man_passw = models.CharField(max_length=16, null=True, blank=True) man_passw = models.CharField(max_length=16, null=True, blank=True)
map_dot = models.ForeignKey(Dot, on_delete=models.SET_NULL, null=True, blank=True) map_dot = models.ForeignKey(Dot, on_delete=models.SET_NULL, null=True, blank=True)
user_group = models.ForeignKey('abonapp.AbonGroup', on_delete=models.SET_NULL, null=True, blank=True) user_group = models.ForeignKey('abonapp.AbonGroup', on_delete=models.SET_NULL, null=True, blank=True)
parent_dev = models.ForeignKey('self', blank=True, null=True, on_delete=models.SET_NULL)
class Meta: class Meta:
db_table = 'dev' db_table = 'dev'
@ -37,25 +49,57 @@ class Device(models.Model):
return res return res
return return
# Можно-ли подключать устройство к абоненту
def has_attachable_to_subscriber(self):
mngr_class = self.get_manager_klass()
return mngr_class.has_attachable_to_subscriber()
def __str__(self): def __str__(self):
return "%s: (%s) %s" % (self.comment, self.get_devtype_display(), self.ip_address)
return "%s: (%s) %s %s" % (self.comment, self.get_devtype_display(), self.ip_address, self.mac_addr or '')
class Port(models.Model): class Port(models.Model):
PORT_SPEEDS = (
('h', '100Mbps'),
('k', '1Gbps'),
('d', '10Gbps')
)
device = models.ForeignKey(Device) device = models.ForeignKey(Device)
num = models.PositiveSmallIntegerField(default=0) num = models.PositiveSmallIntegerField(default=0)
speed = models.CharField(max_length=1, default=PORT_SPEEDS[0][0], choices=PORT_SPEEDS)
descr = models.CharField(max_length=60, null=True, blank=True)
def __str__(self):
return "%d: %s" % (int(self.num), self.descr)
class Meta: class Meta:
db_table = 'dev_port' db_table = 'dev_port'
unique_together = (('device', 'num')) unique_together = (('device', 'num'))
class PortStates(models.Model):
port = models.OneToOneField(Port)
state_json_info = models.TextField()
def dev_post_save_signal(sender, instance, **kwargs):
if instance.devtype != 'On':
return
grp = instance.user_group.pk
code = ''
if grp == 87:
code = 'chk'
elif grp == 85:
code = 'drf'
elif grp == 86:
code = 'eme'
elif grp == 84:
code = 'kunc'
elif grp == 47:
code = 'mtr'
elif grp == 60:
code = 'nvg'
elif grp == 65:
code = 'ohot'
elif grp == 89:
code = 'psh'
elif grp == 92:
code = 'str'
elif grp == 80:
code = 'uy'
elif grp == 79 or grp == 91:
code = 'zrk'
newmac = str(instance.mac_addr)
call(["%s/devapp/onu_register.sh" % settings.BASE_DIR, newmac, code])
models.signals.post_save.connect(dev_post_save_signal, sender=Device)

34
devapp/onu_register.sh

@ -0,0 +1,34 @@
#!/bin/bash
# old mac address
if [[ $1 =~ ^([0-9A-Fa-f]{1,2}[:-]){5}([0-9A-Fa-f]{1,2})$ ]]; then
MAC=$1
else
echo "Bad mac $MAC addr"
exit
fi
# part code
if [[ $2 =~ ^[a-zA-Z]+$ ]]; then
PART_CODE=$2
else
echo 'code must contains only letters'
exit
fi
DHCP_PATH='/etc/dhcp/macs'
PATH=/usr/local/sbin:/usr/local/bin:/usr/bin:/bin
if grep "${MAC}" "${DHCP_PATH}/${PART_CODE}.conf" > /dev/null; then
# mac is already exists
exit
else
# add new mac
echo "subclass \"${PART_CODE}\" \"${MAC}\";" >> "${DHCP_PATH}/${PART_CODE}.conf"
/usr/bin/sudo /usr/bin/systemctl restart dhcpd.service
fi

22
devapp/templates/devapp/add_dev.html

@ -5,13 +5,14 @@
<ol class="breadcrumb"> <ol class="breadcrumb">
<li><span class="glyphicon glyphicon-home"></span></li> <li><span class="glyphicon glyphicon-home"></span></li>
<li><a href="{% url 'devapp:group_list' %}">{% trans 'Groups' %}</a></li> <li><a href="{% url 'devapp:group_list' %}">{% trans 'Groups' %}</a></li>
<li><a href="{% url 'devapp:devs' group.pk %}">{{ group.title }}</a></li>
<li class="active">{% trans 'Add new device' %}</li> <li class="active">{% trans 'Add new device' %}</li>
</ol> </ol>
{% include 'message_block.html' %} {% include 'message_block.html' %}
<div class="page-header"> <div class="page-header">
<h2>{{ dev.comment|default:_('Not assigned') }}</h2>
<h2>{{ group.title|default:_('Not assigned') }}</h2>
</div> </div>
<div class="panel panel-default"> <div class="panel panel-default">
@ -20,7 +21,7 @@
</div> </div>
<div class="panel-body"> <div class="panel-body">
<form role="form" action="{% url 'devapp:add' %}" method="post">{% csrf_token %}
<form role="form" action="{% url 'devapp:add' group.pk %}" method="post" autocomplete="off">{% csrf_token %}
<div class="form-group"> <div class="form-group">
<label for="id_ip_address">{% trans 'Ip address' %}</label> <label for="id_ip_address">{% trans 'Ip address' %}</label>
@ -31,6 +32,23 @@
</div> </div>
</div> </div>
<div class="form-group">
<label for="id_mac_addr">{% trans 'Mac address' %}</label>
<div class="input-group{% if form.mac_addr.errors %} has-error{% endif %}">
<span class="input-group-addon"><span class="glyphicon glyphicon-globe"></span></span>
{{ form.mac_addr }}
{% if already_dev %}
<span class="input-group-btn">
<a class="btn btn-danger" href="{% url 'devapp:view' group.pk already_dev.pk %}" title="{% trans 'View the device' %}" data-toggle="tooltip">
{{ already_dev.comment }}
</a>
</span>
{% endif %}
</div>
{{ form.mac_addr.errors }}
</div>
<div class="form-group"> <div class="form-group">
<label for="id_comment">{% trans 'Comment' %}</label> <label for="id_comment">{% trans 'Comment' %}</label>

18
devapp/templates/devapp/olt.html → devapp/templates/devapp/custom_dev_page/olt.html

@ -2,38 +2,48 @@
{% load i18n %} {% load i18n %}
{% block content %} {% block content %}
<div class="row"> <div class="row">
<div class="col-sm-12"> <div class="col-sm-12">
<div class="table-responsive"> <div class="table-responsive">
{% if uptime %}
{% trans 'Uptime' %} {{ uptime }}
{% endif %}
<table class="table table-striped table-bordered"> <table class="table table-striped table-bordered">
<thead> <thead>
<tr> <tr>
<th width="50">#</th>
<th width="10">#</th>
<th>{% trans 'Mac' %}</th> <th>{% trans 'Mac' %}</th>
<th>{% trans 'Name' %}</th> <th>{% trans 'Name' %}</th>
<th>{% trans 'Distance(m)' %}</th> <th>{% trans 'Distance(m)' %}</th>
<th width="250">{% trans 'Signal' %}</th> <th width="250">{% trans 'Signal' %}</th>
<th width="10">#</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% with dip=dev.ip_address grp=dev.user_group.pk %}
{% for port in ports %} {% for port in ports %}
<tr> <tr>
<td>{% if port.st %}<span class="glyphicon glyphicon-ok text-success"></span> <td>{% if port.st %}<span class="glyphicon glyphicon-ok text-success"></span>
{% else %}<span class="glyphicon glyphicon-remove text-danger"></span>
{% else %}<span class="glyphicon glyphicon-warning-sign text-danger"></span>
{% endif %} {% endif %}
</td> </td>
<td>{{ port.mac }}</td> <td>{{ port.mac }}</td>
<td>{{ port.nm }}</td> <td>{{ port.nm }}</td>
<td>{{ port.sp }}</td> <td>{{ port.sp }}</td>
<td>{{ port.signal }}</td> <td>{{ port.signal }}</td>
<td>
<a href="{% url 'devapp:add' grp %}?mac={{ port.mac }}&t=On&c={{ port.nm }}&ip={{ dip }}" title="Создать устройство">
<span class="glyphicon glyphicon-plus"></span>
</a>
</td>
</tr> </tr>
{% empty %} {% empty %}
<tr> <tr>
<td colspan="5">{% trans 'Ports not found' %}</td>
<td colspan="6">{% trans 'Ports not found' %}</td>
</tr> </tr>
{% endfor %} {% endfor %}
{% endwith %}
</tbody> </tbody>
</table> </table>

42
devapp/templates/devapp/custom_dev_page/onu.html

@ -0,0 +1,42 @@
{% extends request.is_ajax|yesno:'nullcont.htm,devapp/ext.htm' %}
{% load i18n %}
{% block content %}
<div class="row">
<div class="col-sm-12">
<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 }}</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.pk %}" 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.user_group %}
{% trans 'Parent device' %}: <a href="{% url 'devapp:view' pdgrp.pk pdev.pk %}" title="{{ pdev.mac_addr|default:'' }}" target="_blank">{{ pdev.ip_address }} {{ pdev.comment }}</a>
{% endwith %}
</li>
{% endif %}
</ul>
</div>
</div>
</div>
</div>
{% endblock %}

89
devapp/templates/devapp/ports.html → devapp/templates/devapp/custom_dev_page/ports.html

@ -7,36 +7,46 @@
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<div class="panel-title">{{ dev.get_devtype_display|default:_('Title of the type of switch') }}. <div class="panel-title">{{ dev.get_devtype_display|default:_('Title of the type of switch') }}.
{% trans 'Uptime' %} {{ uptime }}</div>
{% if uptime %}
{% trans 'Uptime' %} {{ uptime }}
{% endif %}
</div>
</div> </div>
<div class="panel-body"> <div class="panel-body">
{% for port in ports %} {% for port in ports %}
{% if port.st %} {% if port.st %}
{% if port.sp == 10 %} {% if port.sp == 10 %}
<div class="port kilo">
<div class="port kilo text-center">
<b>10 mbps</b>
{% elif port.sp == 100 %} {% elif port.sp == 100 %}
<div class="port mega">
<div class="port mega text-center">
<b>100 mbps</b>
{% elif port.sp == 1000 %} {% elif port.sp == 1000 %}
<div class="port giga">
<div class="port giga text-center">
<b>1 gbps</b>
{% elif port.sp == 10000 %}
<div class="port ten text-center">
<b>10 gbps</b>
{% else %} {% else %}
<div class="port">
<div class="port text-center">
{% endif %} {% endif %}
{% else %} {% else %}
<div class="port dis">
<div class="port dis text-center">
{% endif %} {% endif %}
<a href="#" class="port-img" title="{{ port.nm }}">
<a href="javascript:void(0);" class="port-img" title="{{ port.nm }}">
<b>{{ port.num }}</b> <b>{{ port.num }}</b>
</a> </a>
<div class="btn-group btn-group-xs btn-group-justified">
<a href="{% url 'devapp:port_toggle' dev.user_group.pk|default:0 dev.id port.num 1 %}" class="btn btn-success">
<span class="glyphicon glyphicon-ok"></span>
</a>
<a href="{% url 'devapp:port_toggle' dev.user_group.pk|default:0 dev.id port.num 0 %}" class="btn btn-danger">
{% if port.st %}
<a href="{% url 'devapp:port_toggle' dev.user_group.pk|default:0 dev.id port.num 0 %}" class="btn btn-xs btn-danger" title="{% trans 'Disable port' %}">
<span class="glyphicon glyphicon-off"></span> <span class="glyphicon glyphicon-off"></span>
</a> </a>
</div>
{% else %}
<a href="{% url 'devapp:port_toggle' dev.user_group.pk|default:0 dev.id port.num 1 %}" class="btn btn-xs btn-success" title="{% trans 'Enable port' %}">
<span class="glyphicon glyphicon-ok"></span>
</a>
{% endif %}
</div> </div>
{% empty %} {% empty %}
<h3>{% trans 'We have not received info, please check options :(' %}</h3> <h3>{% trans 'We have not received info, please check options :(' %}</h3>
@ -65,58 +75,7 @@
</thead> </thead>
<tbody> <tbody>
<tr> <tr>
<td>1</td>
<td>Err:Disable</td>
<td>port Eth1 state Down</td>
<td>21 Dec 12:14:55</td>
</tr>
<tr>
<td>2</td>
<td>Err:bug</td>
<td>port Eth1 state Up</td>
<td>21 Dec 12:15:23</td>
</tr>
<tr>
<td>3</td>
<td>Err:Other</td>
<td>port Eth1 state Up</td>
<td>21 Dec 12:15:45</td>
</tr>
<tr>
<td>1</td>
<td>Err:Disable</td>
<td>port Eth1 state Down</td>
<td>21 Dec 12:14:55</td>
</tr>
<tr>
<td>1</td>
<td>Err:Disable</td>
<td>port Eth1 state Down</td>
<td>21 Dec 12:14:55</td>
</tr>
<tr>
<td>1</td>
<td>Err:Disable</td>
<td>port Eth1 state Down</td>
<td>21 Dec 12:14:55</td>
</tr>
<tr>
<td>1</td>
<td>Err:Disable</td>
<td>port Eth1 state Down</td>
<td>21 Dec 12:14:55</td>
</tr>
<tr>
<td>1</td>
<td>Err:Disable</td>
<td>port Eth1 state Down</td>
<td>21 Dec 12:14:55</td>
</tr>
<tr>
<td>1</td>
<td>Err:Disable</td>
<td>port Eth1 state Down</td>
<td>21 Dec 12:14:55</td>
<td colspan="4">Coming soon..</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

36
devapp/templates/devapp/dev.html

@ -8,7 +8,7 @@
</div> </div>
<div class="panel-body"> <div class="panel-body">
<form role="form" action="{% url 'devapp:edit' dev.user_group.pk|default:0 dev.pk %}" method="post">{% csrf_token %}
<form role="form" action="{% url 'devapp:edit' group.pk|default:0 dev.pk %}" method="post">{% csrf_token %}
<div class="form-group"> <div class="form-group">
<label for="id_ip_address">{% trans 'Ip address' %}</label> <label for="id_ip_address">{% trans 'Ip address' %}</label>
@ -19,6 +19,23 @@
</div> </div>
</div> </div>
<div class="form-group">
<label for="id_mac_addr">{% trans 'Mac address' %}</label>
<div class="input-group{% if form.mac_addr.errors %} has-error{% endif %}">
<span class="input-group-addon"><span class="glyphicon glyphicon-globe"></span></span>
{{ form.mac_addr }}
{% if already_dev %}
<span class="input-group-btn">
<a class="btn btn-danger" href="{% url 'devapp:view' group.pk already_dev.pk %}" title="{% trans 'View the device' %}" data-toggle="tooltip">
{{ already_dev.comment }}
</a>
</span>
{% endif %}
</div>
{{ form.mac_addr.errors }}
</div>
<div class="form-group"> <div class="form-group">
<label for="id_comment">{% trans 'Comment' %}</label> <label for="id_comment">{% trans 'Comment' %}</label>
@ -64,6 +81,23 @@
</div> </div>
</div> </div>
<div class="form-group">
<label for="id_parent_dev">{% trans 'Parent device' %}</label>
<div class="input-group selectajax" data-dst="/dev/search_dev">
<span class="input-group-addon"><span class="glyphicon glyphicon-hdd"></span></span>
<input type="hidden" name="parent_dev" class="selectajax-hid"{% if selected_parent_dev %} value="{{ selected_parent_dev.pk }}"{% endif %}>
{% if selected_parent_dev %}
<button class="selectajax-btn form-control btn btn-default">{{ selected_parent_dev.comment }}</button>
<input type="text" class="form-control dropdown-toggle selectajax-inp hidden" data-toggle="dropdown" id="id_parent_dev" placeholder="{% trans 'Find the device' %}">
{% else %}
<button class="selectajax-btn form-control btn btn-default hidden"></button>
<input type="text" class="form-control dropdown-toggle selectajax-inp" data-toggle="dropdown" id="id_parent_dev" placeholder="{% trans 'Find the device' %}">
{% endif %}
<ul class="dropdown-menu selectajax-ul"></ul>{{ form.parent_dev.errors }}
</div>
</div>
<div class="btn-group"> <div class="btn-group">
<button type="submit" class="btn btn-sm btn-primary"> <button type="submit" class="btn btn-sm btn-primary">
<span class="glyphicon glyphicon-save"></span> {% trans 'Save' %} <span class="glyphicon glyphicon-save"></span> {% trans 'Save' %}

20
devapp/templates/devapp/devices.html

@ -27,6 +27,7 @@
</a> </a>
{% if order_by == 'comment' %}<span class="glyphicon glyphicon-filter"></span>{% endif %} {% if order_by == 'comment' %}<span class="glyphicon glyphicon-filter"></span>{% endif %}
</th> </th>
<th>{% trans 'Mac address' %}</th>
<th width="250"> <th width="250">
<a href="{% url 'devapp:devs' group.pk %}?order_by=devtype&dir={{ dir|default:"down" }}"> <a href="{% url 'devapp:devs' group.pk %}?order_by=devtype&dir={{ dir|default:"down" }}">
{% trans 'Device type' %} {% trans 'Device type' %}
@ -38,19 +39,21 @@
</thead> </thead>
<tbody> <tbody>
{% with can_del_dev=perms.devapp.delete_device can_ch_dev=perms.devapp.change_device %}
{% for dev in devices %} {% for dev in devices %}
<tr> <tr>
<td><a href="{% url 'devapp:view' dev.user_group.pk|default:0 dev.pk %}">{{ dev.ip_address }}</a></td>
<td><a href="{% url 'devapp:view' dev.user_group.pk dev.pk %}">{{ dev.ip_address }}</a></td>
<td>{{ dev.comment }}</td> <td>{{ dev.comment }}</td>
<td>{{ dev.mac_addr }}</td>
<td>{{ dev.get_devtype_display }}</td> <td>{{ dev.get_devtype_display }}</td>
<td class="btn-group btn-group-sm"> <td class="btn-group btn-group-sm">
{% if perms.devapp.delete_device %}
<a href="{% url 'devapp:del' dev.user_group.pk|default:0 dev.pk %}" class="btn btn-default btn-sm">
{% if can_del_dev %}
<a href="{% url 'devapp:del' dev.user_group.pk dev.pk %}" class="btn btn-default btn-sm">
<span class="glyphicon glyphicon-remove"></span> <span class="glyphicon glyphicon-remove"></span>
</a> </a>
{% endif %} {% endif %}
{% if perms.devapp.change_device %}
<a href="{% url 'devapp:edit' dev.user_group.pk|default:0 dev.id %}" class="btn btn-default btn-sm">
{% if can_ch_dev %}
<a href="{% url 'devapp:edit' dev.user_group.pk dev.id %}" class="btn btn-default btn-sm">
<span class="glyphicon glyphicon-edit"></span> <span class="glyphicon glyphicon-edit"></span>
</a> </a>
{% endif %} {% endif %}
@ -58,15 +61,16 @@
</tr> </tr>
{% empty %} {% empty %}
<tr> <tr>
<td colspan="4">{% trans 'Devices does not found' %}. <a href="{% url 'devapp:add' %}">{% trans 'Create' %}</a></td>
<td colspan="5">{% trans 'Devices does not found' %}. <a href="{% url 'devapp:add' group.pk %}">{% trans 'Create' %}</a></td>
</tr> </tr>
{% endfor %} {% endfor %}
{% endwith %}
</tbody> </tbody>
<tfoot> <tfoot>
<tr> <tr>
<td colspan="4">
<a href="{% url 'devapp:add' %}" class="btn btn-success btn-sm">
<td colspan="5">
<a href="{% url 'devapp:add' group.pk %}" class="btn btn-success btn-sm">
<span class="glyphicon glyphicon-plus"></span> {% trans 'Create' %} <span class="glyphicon glyphicon-plus"></span> {% trans 'Create' %}
</a> </a>
</td> </td>

4
devapp/templates/devapp/devices_null_group.html

@ -58,7 +58,7 @@
</tr> </tr>
{% empty %} {% empty %}
<tr> <tr>
<td colspan="4">{% trans 'Devices does not found' %}. <a href="{% url 'devapp:add' %}">{% trans 'Create' %}</a></td>
<td colspan="4">{% trans 'Devices does not found' %}. <a href="{% url 'devapp:add' 0 %}">{% trans 'Create' %}</a></td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
@ -66,7 +66,7 @@
<tfoot> <tfoot>
<tr> <tr>
<td colspan="4"> <td colspan="4">
<a href="{% url 'devapp:add' %}" class="btn btn-success btn-sm">
<a href="{% url 'devapp:add' 0 %}" class="btn btn-success btn-sm">
<span class="glyphicon glyphicon-plus"></span> {% trans 'Create' %} <span class="glyphicon glyphicon-plus"></span> {% trans 'Create' %}
</a> </a>
</td> </td>

9
devapp/templates/devapp/ext.htm

@ -21,7 +21,7 @@
<ul class="nav nav-tabs"> <ul class="nav nav-tabs">
{% url 'devapp:view' dev.user_group.pk|default:0 dev.id as devapp_view %}
{% url 'devapp:view' dev.user_group.pk|default:0 dev.pk as devapp_view %}
<li{% if devapp_view == request.path %} class="active"{% endif %}> <li{% if devapp_view == request.path %} class="active"{% endif %}>
<a href="{{ devapp_view }}"> <a href="{{ devapp_view }}">
{% trans 'Ports' %} {{ dev.ip_address }} {% trans 'Ports' %} {{ dev.ip_address }}
@ -29,10 +29,15 @@
</li> </li>
{% if perms.devapp.change_device %} {% if perms.devapp.change_device %}
{% url 'devapp:edit' dev.user_group.pk|default:0 dev.id as devapp_edit %}
{% url 'devapp:edit' dev.user_group.pk|default:0 dev.pk as devapp_edit %}
<li{% if devapp_edit == request.path %} class="active"{% endif %}> <li{% if devapp_edit == request.path %} class="active"{% endif %}>
<a href="{{ devapp_edit }}">{% trans 'Edit' %}</a> <a href="{{ devapp_edit }}">{% trans 'Edit' %}</a>
</li> </li>
{% url 'devapp:manage_ports' dev.user_group.pk|default:0 dev.pk as devapp_mports %}
<li{% if devapp_mports == request.path %} class="active"{% endif %}>
<a href="{{ devapp_mports }}">{% trans 'Ports' %}</a>
</li>
{% endif %} {% endif %}
</ul> </ul>

93
devapp/templates/devapp/manage_ports/add_ports.html

@ -0,0 +1,93 @@
{% extends request.is_ajax|yesno:'bajax.html,base.html' %}
{% load i18n %}
{% block main %}
<ol class="breadcrumb">
<li><span class="glyphicon glyphicon-home"></span></li>
<li><a href="{% url 'devapp:group_list' %}">{% trans 'Groups' %}</a></li>
<li><a href="{% url 'devapp:devs' dev.user_group.pk %}">{{ dev.user_group.title }}</a></li>
<li><a href="{% url 'devapp:view' dev.user_group.pk dev.pk %}">{{ dev.comment }}</a></li>
<li class="active">{% trans 'Add ports' %}</li>
</ol>
{% include 'message_block.html' %}
<div class="page-header">
<h2>{{ dev.comment|default:_('Not assigned') }}</h2>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{{ dev.comment }}</h3>
</div>
<div class="panel-body">
<form class="table-responsive" role="form" action="{% url 'devapp:add_ports' dev.user_group.pk dev.pk %}" method="post">{% csrf_token %}
<table class="table table-striped table-bordered">
<thead>
<tr>
<th width="10">#</th>
<th></th>
<th>{% trans 'Mode' %}</th>
<th>{% trans 'Description' %}</th>
</tr>
</thead>
<tbody>
{% with gid=dev.user_group.pk did=dev.pk can_del_port=perms.devapp.delete_port %}
{% for port in ports %}
<tr>
<td>{% if port.status %}
<span class="glyphicon glyphicon-ok text-success"></span>
{% else %}
<span class="glyphicon glyphicon-warning-sign text-danger"></span>
{% endif %}</td>
<td>{{ port.pid }}</td>
<td>{{ port.mode }}</td>
<td class="input-group">
<input type="text" class="form-control input-sm" name="p_text" value="{{ port.text }}">
<input type="hidden" name="pids" value="{{ port.pid }}">
<span class="input-group-btn">
{% if port.from_db %}
{% if can_del_port %}
<a href="{% url 'devapp:del_port' gid did port.pk %}" class="btn btn-sm btn-danger btn-modal" title="{% trans 'Delete' %}">
{% else %}
<a href="#" class="btn btn-danger btn-sm disabled" title="{% trans 'Delete' %}">
{% endif %}
<span class="glyphicon glyphicon-remove"></span>
</a>
{% else %}
<a href="{% url 'devapp:add_port' gid did %}?n={{ port.pid }}&t={{ port.text }}" class="btn btn-sm btn-success btn-modal" title="{% trans 'Add' %}">
<span class="glyphicon glyphicon-plus"></span>
</a>
{% endif %}
</span>
</td>
</tr>
{% endfor %}
{% endwith %}
</tbody>
<tfoot>
<tr>
<td colspan="4" class="btn-group">
{% if perms.devapp.add_port and ports %}
<button type="submit" class="btn btn-primary">
<span class="glyphicon glyphicon-save"></span> {% trans 'Save' %}
</button>
{% else %}
<button type="button" class="btn btn-primary" disabled>
<span class="glyphicon glyphicon-save"></span> {% trans 'Save' %}
</button>
{% endif %}
</td>
</tr>
</tfoot>
</table>
</form>
</div>
</div>
{% endblock %}

60
devapp/templates/devapp/manage_ports/list.html

@ -0,0 +1,60 @@
{% extends request.is_ajax|yesno:'nullcont.htm,devapp/ext.htm' %}
{% load i18n %}
{% block content %}
<div class="row">
<div class="col-sm-12">
<div class="table-responsive">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th width="50">{% trans 'Number' %}</th>
<th>{% trans 'Description' %}</th>
<th width="100">#</th>
</tr>
</thead>
<tbody>
{% with gid=dev.user_group.pk did=dev.pk can_del_port=perms.devapp.delete_port can_edit_port=perms.devapp.change_port %}
{% for port in ports %}
<tr>
<td>{{ port.num }}</td>
<td>{{ port.descr }}</td>
<td class="btn-group btn-group-sm">
{% if can_del_port %}
<a href="{% url 'devapp:del_port' gid did port.pk %}" class="btn btn-danger btn-modal" title="{% trans 'Delete' %}">
<span class="glyphicon glyphicon-remove-circle"></span>
</a>
{% endif %}
{% if can_edit_port %}
<a href="{% url 'devapp:edit_port' gid did port.pk %}" class="btn btn-primary btn-modal" title="{% trans 'Edit' %}">
<span class="glyphicon glyphicon-edit"></span>
</a>
{% endif %}
</td>
</tr>
{% empty %}
<tr>
<td colspan="3">{% trans 'Ports not found' %}</td>
</tr>
{% endfor %}
{% endwith %}
</tbody>
<tfoot>
<tr>
<td colspan="3" class="btn-group">
{% if perms.devapp.add_port %}
<a href="{% url 'devapp:add_ports' dev.user_group.pk dev.pk %}" class="btn btn-sm btn-default" title="{% trans 'Add' %}">
<span class="glyphicon glyphicon-plus"></span> {% trans 'Add ports' %}
</a>
{% endif %}
</td>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
{% endblock %}

37
devapp/templates/devapp/manage_ports/modal_add_edit_port.html

@ -0,0 +1,37 @@
{% load i18n %}
{% if port_id %}
<form role="form" action="{% url 'devapp:edit_port' gid did port_id %}" method="post">{% else %}
<form role="form" action="{% url 'devapp:add_port' gid did %}" method="post">{% endif %}{% csrf_token %}
<input type="hidden" value="yes" name="confirm">
<div class="modal-header primary">
<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 'Are you sure?' %}</h4>
</div>
<div class="modal-body">
<div class="form-group">
<label for="id_num">{% trans 'Number' %}</label>
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-bishop"></span></span>
{{ form.num }}{{ form.num.errors }}
</div>
</div>
<div class="form-group">
<label for="id_descr">{% trans 'Description' %}</label>
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-comment"></span></span>
{{ form.descr }}{{ form.descr.errors }}
</div>
</div>
<button type="submit" class="btn btn-sm btn-primary">
<span class="glyphicon glyphicon-save"></span> {% trans 'Save' %}
</button>
</div>
</form>

18
devapp/templates/devapp/manage_ports/modal_del_port.html

@ -0,0 +1,18 @@
{% load i18n %}
<form role="form" action="{% url 'devapp:del_port' grp did port_id %}" method="post">{% csrf_token %}
<input type="hidden" value="yes" name="confirm">
<div class="modal-header primary">
<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 'Are you sure?' %}</h4>
</div>
<div class="modal-body">
<p>{% trans 'Are you sure that you want to delete switch port from db?' %}</p>
<button type="submit" class="btn btn-sm btn-danger">
<span class="glyphicon glyphicon-remove"></span> {% trans 'Delete' %}
</button>
</div>
</form>

12
devapp/urls.py

@ -5,11 +5,17 @@ from . import views
urlpatterns = [ urlpatterns = [
url(r'^$', views.group_list, name='group_list'), url(r'^$', views.group_list, name='group_list'),
url(r'^add$', views.dev, name='add'),
url(r'^devices_without_groups$', views.devices_null_group, name='devices_null_group'), url(r'^devices_without_groups$', views.devices_null_group, name='devices_null_group'),
url(r'^(?P<grp>\d+)$', views.devices, name='devs'), url(r'^(?P<grp>\d+)$', views.devices, name='devs'),
url(r'^(?P<grp>\d+)/add$', views.dev, name='add'),
url(r'^(\d+)/(?P<did>\d+)$', views.devview, name='view'), url(r'^(\d+)/(?P<did>\d+)$', views.devview, name='view'),
url(r'^(\d+)/(?P<did>\d+)/del$', views.devdel, name='del'), url(r'^(\d+)/(?P<did>\d+)/del$', views.devdel, name='del'),
url(r'^(\d+)/(?P<devid>\d+)/edit$', views.dev, name='edit'),
url(r'^(\d+)/(?P<did>\d+)/(?P<portid>\d+)_(?P<status>[0-1]{1})$', views.toggle_port, name='port_toggle')
url(r'^(?P<grp>\d+)/(?P<did>\d+)/add$', views.add_single_port, name='add_port'),
url(r'^(?P<grp>\d+)/(?P<devid>\d+)/edit$', views.dev, name='edit'),
url(r'^(\d+)/(?P<devid>\d+)/ports$', views.manage_ports, name='manage_ports'),
url(r'^(\d+)/(?P<devid>\d+)/ports_add', views.add_ports, name='add_ports'),
url(r'^(\d+)/(?P<did>\d+)/(?P<portid>\d+)_(?P<status>[0-1]{1})$', views.toggle_port, name='port_toggle'),
url(r'^(?P<grp>\d+)/(?P<did>\d+)/(?P<portid>\d+)/del$', views.delete_single_port, name='del_port'),
url(r'^(?P<grp>\d+)/(?P<did>\d+)/(?P<pid>\d+)/edit$', views.edit_single_port, name='edit_port'),
url(r'^search_dev$', views.search_dev)
] ]

236
devapp/views.py

@ -1,15 +1,20 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from django.contrib.auth.decorators import login_required, permission_required from django.contrib.auth.decorators import login_required, permission_required
from django.contrib.gis.shortcuts import render_to_text
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.db.models import Q
from django.http import HttpResponse
from django.shortcuts import render, redirect, get_object_or_404, resolve_url from django.shortcuts import render, redirect, get_object_or_404, resolve_url
from django.contrib import messages from django.contrib import messages
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from easysnmp import EasySNMPTimeoutError, EasySNMPError from easysnmp import EasySNMPTimeoutError, EasySNMPError
from json import dumps
from .models import Device
from .models import Device, Port, DeviceDBException
from mydefs import pag_mn, res_success, res_error, only_admins, ping, order_helper from mydefs import pag_mn, res_success, res_error, only_admins, ping, order_helper
from .forms import DeviceForm
from abonapp.models import AbonGroup
from .forms import DeviceForm, PortForm
from abonapp.models import AbonGroup, Abon
from djing.settings import DEFAULT_SNMP_PASSWORD
@login_required @login_required
@ -61,12 +66,16 @@ def devdel(request, did):
return res_success(request, back_url) return res_success(request, back_url)
except Device.DoesNotExist: except Device.DoesNotExist:
return res_error(request, _('Delete failed')) return res_error(request, _('Delete failed'))
except DeviceDBException as e:
return res_error(request, e)
@login_required @login_required
@only_admins @only_admins
def dev(request, devid=0):
def dev(request, grp, devid=0):
devinst = get_object_or_404(Device, id=devid) if devid != 0 else None devinst = get_object_or_404(Device, id=devid) if devid != 0 else None
user_group = get_object_or_404(AbonGroup, pk=grp)
already_dev = None
if request.method == 'POST': if request.method == 'POST':
if devid == 0: if devid == 0:
@ -77,32 +86,220 @@ def dev(request, devid=0):
raise PermissionDenied raise PermissionDenied
frm = DeviceForm(request.POST, instance=devinst) frm = DeviceForm(request.POST, instance=devinst)
if frm.is_valid(): if frm.is_valid():
frm.save()
ndev = frm.save()
messages.success(request, _('Device info has been saved')) messages.success(request, _('Device info has been saved'))
return redirect('devapp:edit', grp, ndev.pk)
else: else:
try:
already_dev = Device.objects.get(mac_addr=request.POST.get('mac_addr'))
except Device.DoesNotExist:
pass
messages.error(request, _('Form is invalid, check fields and try again')) messages.error(request, _('Form is invalid, check fields and try again'))
else:
if devinst is None:
frm = DeviceForm(initial={
'user_group': user_group,
'devtype': request.GET.get('t'),
'mac_addr': request.GET.get('mac'),
'comment': request.GET.get('c'),
'ip_address': request.GET.get('ip'),
'man_passw': DEFAULT_SNMP_PASSWORD
})
else: else:
frm = DeviceForm(instance=devinst) frm = DeviceForm(instance=devinst)
if devinst is None: if devinst is None:
return render(request, 'devapp/add_dev.html', { return render(request, 'devapp/add_dev.html', {
'form': frm
'form': frm,
'group': user_group,
'already_dev': already_dev
}) })
else: else:
return render(request, 'devapp/dev.html', { return render(request, 'devapp/dev.html', {
'form': frm, 'form': frm,
'dev': devinst
'dev': devinst,
'selected_parent_dev': devinst.parent_dev or None,
'group': user_group,
'already_dev': already_dev
})
@login_required
@permission_required('devapp.change_device')
def manage_ports(request, devid):
try:
dev = Device.objects.get(pk=devid)
if dev.user_group is None:
messages.error(request, _('Device is not have a group, please fix that'))
return redirect('devapp:group_list')
ports = Port.objects.filter(device=dev)
except Device.DoesNotExist:
messages.error(request, _('Device does not exist'))
return redirect('devapp:view', dev.user_group.pk if dev.user_group else 0, did=devid)
except DeviceDBException as e:
messages.error(request, e)
return render(request, 'devapp/manage_ports/list.html', {
'ports': ports,
'dev': dev
})
@login_required
@permission_required('devapp.add_port')
def add_ports(request, devid):
class TempPort:
def __init__(self, pid, text, status, from_db, pk=None):
self.pid = pid
self.text = text
self.status = status
self.from_db = from_db
self.pk = pk
def __eq__(self, other):
return self.pid == other.pid
def __hash__(self):
return self.pid
def __str__(self):
return "p:%d\tM:%s\tT:%s" % (self.pid, self.text)
try:
res_ports = list()
dev = Device.objects.get(pk=devid)
if dev.user_group is None:
messages.error(request, _('Device is not have a group, please fix that'))
return redirect('devapp:group_list')
if request.method == 'POST':
ports = zip(
request.POST.getlist('p_text'),
request.POST.getlist('pids')
)
for port_text, port_num in ports:
if port_text == '' or port_text is None:
continue
try:
port = Port.objects.get(num=port_num, device=dev)
port.descr = port_text
port.save(update_fields=['descr'])
except Port.DoesNotExist:
Port.objects.create(
num=port_num,
device=dev,
descr=port_text
)
db_ports = Port.objects.filter(device=dev)
db_ports = [TempPort(p.num, p.descr, None, True, p.pk) for p in db_ports]
manager = dev.get_manager_klass()(dev.ip_address, dev.man_passw)
ports = manager.get_ports()
if ports is not None:
ports = [TempPort(p.num, p.nm, p.st, False) for p in ports]
res_ports = set(db_ports + ports)
else:
res_ports = db_ports
except Device.DoesNotExist:
messages.error(request, _('Device does not exist'))
return redirect('devapp:group_list')
except DeviceDBException as e:
messages.error(request, e)
except EasySNMPTimeoutError:
messages.error(request, _('wait for a reply from the SNMP Timeout'))
return render(request, 'devapp/manage_ports/add_ports.html', {
'ports': res_ports,
'dev': dev
})
@login_required
@permission_required('devapp.delete_port')
def delete_single_port(request, grp, did, portid):
try:
if request.method == 'POST':
if request.POST.get('confirm') == 'yes':
Port.objects.get(pk=portid).delete()
messages.success(request, _('Port successfully removed'))
else:
return render_to_text('devapp/manage_ports/modal_del_port.html', {
'grp': grp,
'did': did,
'port_id': portid
}, request=request)
except Port.DoesNotExist:
messages.error(request, _('Port does not exist'))
except DeviceDBException as e:
messages.error(request, e)
return redirect('devapp:manage_ports', grp, did)
@login_required
@permission_required('devapp.add_port')
def edit_single_port(request, grp, did, pid):
try:
port = Port.objects.get(pk=pid)
if request.method == 'POST':
frm = PortForm(request.POST, instance=port)
if frm.is_valid():
frm.save()
messages.success(request, _('Port successfully saved'))
else:
messages.error(request, _('Form is invalid, check fields and try again'))
return redirect('devapp:manage_ports', grp, did)
frm = PortForm(instance=port)
return render_to_text('devapp/manage_ports/modal_add_edit_port.html', {
'port_id': pid,
'did': did,
'gid': grp,
'form': frm
}, request=request)
except Port.DoesNotExist:
messages.error(request, _('Port does not exist'))
except DeviceDBException as e:
messages.error(request, e)
return redirect('devapp:manage_ports', grp, did)
@login_required
@permission_required('devapp.add_port')
def add_single_port(request, grp, did):
try:
device = Device.objects.get(pk=did)
if request.method == 'POST':
frm = PortForm(request.POST, instance=Port(device=device))
if frm.is_valid():
frm.save()
messages.success(request, _('Port successfully saved'))
return redirect('devapp:manage_ports', grp, did)
else:
messages.error(request, _('Form is invalid, check fields and try again'))
else:
frm = PortForm(initial={
'num': request.GET.get('n'),
'descr': request.GET.get('t')
}) })
return render_to_text('devapp/manage_ports/modal_add_edit_port.html', {
'did': did,
'gid': grp,
'form': frm
}, request=request)
except Device.DoesNotExist:
messages.error(request, _('Device does not exist'))
except DeviceDBException as e:
messages.error(request, e)
return redirect('devapp:manage_ports', grp, did)
@login_required @login_required
@only_admins @only_admins
def devview(request, did): def devview(request, did):
ports = None ports = None
uptime = 0 uptime = 0
dev = get_object_or_404(Device, id=did) dev = get_object_or_404(Device, id=did)
template_name = 'devapp/ports.html'
template_name = 'ports.html'
try: try:
if ping(dev.ip_address): if ping(dev.ip_address):
if dev.man_passw: if dev.man_passw:
@ -118,11 +315,14 @@ def devview(request, did):
messages.error(request, _('wait for a reply from the SNMP Timeout')) messages.error(request, _('wait for a reply from the SNMP Timeout'))
except EasySNMPError: except EasySNMPError:
messages.error(request, _('SNMP error on device')) messages.error(request, _('SNMP error on device'))
except DeviceDBException as e:
messages.error(request, e)
return render(request, template_name, {
return render(request, 'devapp/custom_dev_page/'+template_name, {
'dev': dev, 'dev': dev,
'ports': ports, 'ports': ports,
'uptime': uptime
'uptime': uptime,
'dev_accs': Abon.objects.filter(device=dev)
}) })
@ -132,6 +332,7 @@ def toggle_port(request, did, portid, status=0):
portid = int(portid) portid = int(portid)
status = int(status) status = int(status)
dev = get_object_or_404(Device, id=int(did)) dev = get_object_or_404(Device, id=int(did))
try:
if ping(dev.ip_address): if ping(dev.ip_address):
if dev.man_passw: if dev.man_passw:
manager = dev.get_manager_klass()(dev.ip_address, dev.man_passw) manager = dev.get_manager_klass()(dev.ip_address, dev.man_passw)
@ -144,6 +345,8 @@ def toggle_port(request, did, portid, status=0):
messages.warning(request, _('Not Set snmp device password')) messages.warning(request, _('Not Set snmp device password'))
else: else:
messages.error(request, _('Dot was not pinged')) messages.error(request, _('Dot was not pinged'))
except EasySNMPTimeoutError:
messages.error(request, _('wait for a reply from the SNMP Timeout'))
return redirect('devapp:view', dev.user_group.pk if dev.user_group is not None else 0, did) return redirect('devapp:view', dev.user_group.pk if dev.user_group is not None else 0, did)
@ -154,3 +357,14 @@ def group_list(request):
return render(request, 'devapp/group_list.html', { return render(request, 'devapp/group_list.html', {
'groups': groups 'groups': groups
}) })
@login_required
def search_dev(request):
word = request.GET.get('s')
if word is None:
results = [{'id': 0, 'text': ''}]
else:
results = Device.objects.filter(Q(comment__icontains=word) | Q(ip_address=word))[:16]
results = [{'id': dev.pk, 'text': "%s: %s" % (dev.ip_address, dev.comment)} for dev in results]
return HttpResponse(dumps(results, ensure_ascii=False))

55
dialing_app/locale/ru/LC_MESSAGES/django.po

@ -19,75 +19,90 @@ msgstr ""
"%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n" "%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" "%100>=11 && n%100<=14)? 2 : 3);\n"
#: dialing_app/models.py:8
#: dialing_app/models.py:8 dialing_app/models.py:43
msgid "No answer" msgid "No answer"
msgstr "Не отвечен" msgstr "Не отвечен"
#: dialing_app/models.py:9
#: dialing_app/models.py:9 dialing_app/models.py:45
msgid "Failed" msgid "Failed"
msgstr "С ошибкой" msgstr "С ошибкой"
#: dialing_app/models.py:10
#: dialing_app/models.py:10 dialing_app/models.py:47
msgid "Busy" msgid "Busy"
msgstr "Занято" msgstr "Занято"
#: dialing_app/models.py:11
#: dialing_app/models.py:11 dialing_app/models.py:49
msgid "Answered" msgid "Answered"
msgstr "Отвечен" msgstr "Отвечен"
#: dialing_app/models.py:12
#: dialing_app/models.py:12 dialing_app/models.py:51
msgid "Unknown" msgid "Unknown"
msgstr "Не определён" msgstr "Не определён"
#: dialing_app/templates/index.html:8
#: dialing_app/templates/index.html:9
msgid "Dialing" msgid "Dialing"
msgstr "Звонки" msgstr "Звонки"
#: dialing_app/templates/index.html:11
#: dialing_app/templates/index.html:12
msgid "Last calls" msgid "Last calls"
msgstr "Последние звонки" msgstr "Последние звонки"
#: dialing_app/templates/index.html:19
#: dialing_app/templates/index.html:20
msgid "Play"
msgstr "Слушать"
#: dialing_app/templates/index.html:21
msgid "calldate" msgid "calldate"
msgstr "дата звонка" msgstr "дата звонка"
#: dialing_app/templates/index.html:20
#: dialing_app/templates/index.html:22
msgid "src" msgid "src"
msgstr "кто" msgstr "кто"
#: dialing_app/templates/index.html:21
#: dialing_app/templates/index.html:23
msgid "dst" msgid "dst"
msgstr "куда" msgstr "куда"
#: dialing_app/templates/index.html:26
#: dialing_app/templates/index.html:24
msgid "duration" msgid "duration"
msgstr "продолжительность" msgstr "продолжительность"
#: dialing_app/templates/index.html:28
#: dialing_app/templates/index.html:25
msgid "start" msgid "start"
msgstr "начало" msgstr "начало"
#: dialing_app/templates/index.html:29
#: dialing_app/templates/index.html:26
msgid "answer" msgid "answer"
msgstr "ответ" msgstr "ответ"
#: dialing_app/templates/index.html:30
#: dialing_app/templates/index.html:27
msgid "end" msgid "end"
msgstr "конец" msgstr "конец"
#: dialing_app/templates/index.html:31
#: dialing_app/templates/index.html:28
msgid "disposition" msgid "disposition"
msgstr "состояние" msgstr "состояние"
#: dialing_app/templates/index.html:57
#: dialing_app/templates/index.html:50
msgid "Calls was not found" msgid "Calls was not found"
msgstr "Звонки не найдены" msgstr "Звонки не найдены"
msgid "Play"
msgstr "Слушать"
#: dialing_app/views.py:27
msgid "Multiple users with the telephone number"
msgstr "Несколько абонентов с указанным номером телефона"
#: dialing_app/views.py:29
msgid "User with the telephone number not found" msgid "User with the telephone number not found"
msgstr "Абонент с таким номером телефона не найден" msgstr "Абонент с таким номером телефона не найден"
msgid "Multiple users with the telephone number"
msgstr "Несколько абонентов с указанным номером телефона"
msgid "Voice mail"
msgstr "Оставленные сообщения"
msgid "Type"
msgstr "Тип"
msgid "Request"
msgstr "Заявка"
msgid "Report"
msgstr "Поломка"

43
dialing_app/migrations/0001_initial.py

@ -0,0 +1,43 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2017-05-30 13:33
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='AsteriskCDR',
fields=[
('calldate', models.DateTimeField(default='0000-00-00 00:00:00', primary_key=True, serialize=False)),
('clid', models.CharField(default='', max_length=80)),
('src', models.CharField(default='', max_length=80)),
('dst', models.CharField(default='', max_length=80)),
('dcontext', models.CharField(default='', max_length=80)),
('channel', models.CharField(default='', max_length=80)),
('dstchannel', models.CharField(default='', max_length=80)),
('lastapp', models.CharField(default='', max_length=80)),
('lastdata', models.CharField(default='', max_length=80)),
('duration', models.IntegerField(default=0)),
('billsec', models.IntegerField(default=0)),
('start', models.DateTimeField(blank=True, default=None, null=True)),
('answer', models.DateTimeField(blank=True, default=None, null=True)),
('end', models.DateTimeField(blank=True, default=None, null=True)),
('disposition', models.CharField(choices=[('NO ANSWER', 'No answer'), ('FAILED', 'Failed'), ('BUSY', 'Busy'), ('ANSWERED', 'Answered'), ('UNKNOWN', 'Unknown')], default='', max_length=45)),
('amaflags', models.IntegerField(default=0)),
('accountcode', models.CharField(default='', max_length=20)),
('userfield', models.CharField(default='', max_length=255)),
('uniqueid', models.CharField(default='', max_length=32)),
],
options={
'db_table': 'cdr',
},
),
]

10
dialing_app/models.py

@ -51,9 +51,13 @@ class AsteriskCDR(models.Model):
return _('Unknown') return _('Unknown')
return '' return ''
@staticmethod
def path_to_media():
return getattr(settings, 'DIALING_MEDIA', '/media')
def path_to_media(self):
path = getattr(settings, 'DIALING_MEDIA', '/media')
if self.userfield == 'request':
return "%s/recording/request" % path
elif self.userfield == 'report':
return "%s/recording/bug" % path
return "%s/monitor" % path
class Meta: class Meta:
db_table = 'cdr' db_table = 'cdr'

40
dialing_app/templates/ext.html

@ -0,0 +1,40 @@
{% extends request.is_ajax|yesno:'bajax.html,base.html' %}
{% load i18n %}
{% block main %}
<ol class="breadcrumb">
<li><span class="glyphicon glyphicon-home"></span></li>
<li class="active">{% trans 'Last calls' %}</li>
</ol>
{% include 'message_block.html' %}
<div class="page-header">
<h3>{{ title }}</h3>
</div>
<ul class="nav nav-tabs">
{% url 'dialapp:home' as dialhome %}
<li{% if dialhome == request.path %} class="active"{% endif %}>
<a href="{{ dialhome }}">
{% trans 'Last calls' %}
</a>
</li>
{% url 'dialapp:vmail' as dialmail %}
<li{% if dialmail == request.path %} class="active"{% endif %}>
<a href="{{ dialmail }}">
{% trans 'Voice mail' %}
</a>
</li>
</ul>
<div class="tab-content">
<div class="tab-pane active">
{% block content %}{% endblock %}
</div>
</div>
{% endblock %}

26
dialing_app/templates/index.html

@ -1,17 +1,7 @@
{% extends 'base.html' %}
{% extends request.is_ajax|yesno:'nullcont.htm,ext.html' %}
{% load i18n %} {% load i18n %}
{% load telephone_filters %} {% load telephone_filters %}
{% block main %}
<ol class="breadcrumb">
<li><span class="glyphicon glyphicon-home"></span></li>
<li class="active">{% trans 'Dialing' %}</li>
</ol>
<h3>{% trans 'Last calls' %}</h3>
{% include 'message_block.html' %}
{% block content %}
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-striped table-bordered"> <table class="table table-striped table-bordered">
@ -31,10 +21,14 @@
<tbody> <tbody>
{% for log in logs %} {% for log in logs %}
<tr> <tr>
<td>
<audio preload="metadata" controls>
<source src="{{ log.path_to_media }}/{{ log.calldate|date:"YmdHi" }}-{{ log.src }}-{{ log.dst }}.wav" type="audio/wav"/>
</audio>
<td class="btn-group btn-group-sm btn-group-justify">
<button class="btn btn-default player-btn disabled">
<span class="glyphicon glyphicon-play"></span>
<audio preload="metadata" src="{{ log.path_to_media }}/{{ log.calldate|date:"YmdHi" }}-{{ log.src }}-{{ log.dst }}.wav"></audio>
</button>
<a href="{{ log.path_to_media }}/{{ log.calldate|date:"YmdHi" }}-{{ log.src }}-{{ log.dst }}.wav" class="btn btn-default disabled" target="_blank">
<span class="glyphicon glyphicon-download-alt"></span>
</a>
</td> </td>
<td>{{ log.calldate|date:'d E Y, H:i:s' }}</td> <td>{{ log.calldate|date:'d E Y, H:i:s' }}</td>
<td>{{ log.src|abon_if_telephone|safe }}</td> <td>{{ log.src|abon_if_telephone|safe }}</td>

57
dialing_app/templates/vmail.html

@ -0,0 +1,57 @@
{% extends request.is_ajax|yesno:'nullcont.htm,ext.html' %}
{% load i18n %}
{% load telephone_filters %}
{% block content %}
<div class="table-responsive">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>{% trans 'Play' %}</th>
<th>{% trans 'calldate' %}</th>
<th>{% trans 'src' %}</th>
<th>{% trans 'Type' %}</th>
<th>{% trans 'duration' %}</th>
<th>{% trans 'start' %}</th>
<th>{% trans 'answer' %}</th>
<th>{% trans 'end' %}</th>
<th>{% trans 'disposition' %}</th>
</tr>
</thead>
<tbody>
{% for vmail in vmessages %}
<tr>
<td class="btn-group btn-group-sm">
<button class="btn btn-default player-btn disabled">
<span class="glyphicon glyphicon-play"></span>
<audio preload="metadata" src="{{ vmail.path_to_media }}/{{ vmail.calldate|date:"YmdHi" }}-{{ vmail.src }}-{{ vmail.dst }}.wav"></audio>
</button>
<a href="{{ vmail.path_to_media }}/{{ vmail.calldate|date:"YmdHi" }}-{{ vmail.src }}-{{ vmail.dst }}.wav" class="btn btn-default disabled" target="_blank">
<span class="glyphicon glyphicon-download-alt"></span>
</a>
</td>
<td>{{ vmail.calldate|date:'d E Y, H:i:s' }}</td>
<td>{{ vmail.src|abon_if_telephone|safe }}</td>
<td>
{% if vmail.userfield == 'request' %}{% trans 'Request' %}
{% elif vmail.userfield == 'report' %}{% trans 'Report' %}
{% else %}{{ vmail.userfield }}{% endif %}
</td>
<td>{{ vmail.duration }}</td>
<td>{{ vmail.start|date:'d E Y, H:i:s' }}</td>
<td>{{ vmail.answer|date:'d E Y, H:i:s' }}</td>
<td>{{ vmail.end|date:'d E Y, H:i:s' }}</td>
<td>{{ vmail.locate_disposition }}</td>
</tr>
{% empty %}
<tr>
<td colspan="9">{% trans 'Calls was not found' %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% include 'toolbar_page.html' with pag=vmessages %}
{% endblock %}

2
dialing_app/templatetags/telephone_filters.py

@ -9,14 +9,12 @@ register = template.Library()
@register.filter @register.filter
@stringfilter @stringfilter
def abon_if_telephone(value): def abon_if_telephone(value):
print(value, type(value))
"""Возвращаем ссыль на абонента если передали номер телефона""" """Возвращаем ссыль на абонента если передали номер телефона"""
if re.match(r'^\+?\d+$', value): if re.match(r'^\+?\d+$', value):
if value[0] != '+': if value[0] != '+':
value = '+'+value value = '+'+value
url = resolve_url('dialapp:to_abon', tel=value) url = resolve_url('dialapp:to_abon', tel=value)
a = '<a href="%s" target="_blank">%s</a>' % (url, value) a = '<a href="%s" target="_blank">%s</a>' % (url, value)
print(a)
return a return a
else: else:
return value return value

3
dialing_app/urls.py

@ -4,5 +4,6 @@ from . import views
urlpatterns = [ urlpatterns = [
url(r'^$', views.home, name='home'), url(r'^$', views.home, name='home'),
url(r'^to_abon(?P<tel>\+?\d+)$', views.to_abon, name='to_abon')
url(r'^to_abon(?P<tel>\+?\d+)$', views.to_abon, name='to_abon'),
url(r'^voicemail$', views.vmail, name='vmail')
] ]

17
dialing_app/views.py

@ -11,10 +11,12 @@ from .models import AsteriskCDR
@login_required @login_required
@only_admins @only_admins
def home(request): def home(request):
logs = AsteriskCDR.objects.all()
logs = AsteriskCDR.objects.exclude(userfield='request').order_by('-calldate')
logs = pag_mn(request, logs) logs = pag_mn(request, logs)
title = _('Last calls')
return render(request, 'index.html', { return render(request, 'index.html', {
'logs': logs
'logs': logs,
'title': title
}) })
@ -34,3 +36,14 @@ def to_abon(request, tel):
else: else:
return redirect('abonapp:group_list') return redirect('abonapp:group_list')
@login_required
@only_admins
def vmail(request):
title = _('Voice mail')
cdr = AsteriskCDR.objects.filter(userfield='request').order_by('-calldate')
cdr = pag_mn(request, cdr)
return render(request, 'vmail.html', {
'title': title,
'vmessages': cdr
})

38
djing/__init__.py

@ -0,0 +1,38 @@
import importlib
from netaddr import mac_unix, mac_eui48
MAC_ADDR_REGEX = r'^([0-9A-Fa-f]{1,2}[:-]){5}([0-9A-Fa-f]{1,2})$'
class mac_linux(mac_unix):
"""MAC format with zero-padded all upper-case hex and colon separated"""
word_fmt = '%x'
def default_dialect(eui_obj=None):
return mac_linux
def format_mac(eui_obj, dialect):
# Format a EUI instance as a string using the supplied dialect class, allowing custom string classes by
# passing directly or as a string, a la 'module.dialect_cls', where 'module' is the module and 'dialect_cls'
# is the class name of the custom dialect. The dialect must either be defined or imported by the module's __init__.py if
# the module is a package.
if not isinstance(dialect, mac_eui48):
if isinstance(dialect, str):
module, dialect_cls = dialect.split('.')
dialect = getattr(importlib.import_module(module), dialect_cls)
eui_obj.dialect = dialect
return str(eui_obj)
from pkg_resources import get_distribution, DistributionNotFound
try:
_dist = get_distribution('django-macaddress')
except DistributionNotFound:
__version__ = 'Please install this project with setup.py'
else:
__version__ = _dist.version
VERSION = __version__ # synonym
default_app_config = 'abonapp.apps.AbonappConfig'

0
abonapp/fields.py → djing/fields.py

3
abonapp/formfields.py → djing/formfields.py

@ -5,10 +5,11 @@ from django.forms.fields import EMPTY_VALUES
from django.forms.utils import ValidationError from django.forms.utils import ValidationError
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from netaddr import EUI, AddrFormatError from netaddr import EUI, AddrFormatError
from . import MAC_ADDR_REGEX
mac_address_validator = RegexValidator( mac_address_validator = RegexValidator(
_lazy_re_compile(r'^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$'),
_lazy_re_compile(MAC_ADDR_REGEX),
message=_('Enter a valid integer.'), message=_('Enter a valid integer.'),
code='invalid', code='invalid',
) )

4
djing/settings_example.py

@ -162,3 +162,7 @@ pay_SERV_ID = '<service id>'
pay_SECRET = '<secret>' pay_SECRET = '<secret>'
DIALING_MEDIA = 'path/to/asterisk_records' DIALING_MEDIA = 'path/to/asterisk_records'
DHCP_TIMEOUT = 14400
DEFAULT_SNMP_PASSWORD = 'public'

2
djing/urls.py

@ -17,7 +17,7 @@ urlpatterns = [
url(r'^tasks/', include('taskapp.urls', namespace='taskapp')), url(r'^tasks/', include('taskapp.urls', namespace='taskapp')),
url(r'^client/', include('clientsideapp.urls', namespace='client_side')), url(r'^client/', include('clientsideapp.urls', namespace='client_side')),
url(r'^msg/', include('django_messages.urls', namespace='django_messages')), url(r'^msg/', include('django_messages.urls', namespace='django_messages')),
url(r'dialing/', include('dialing_app.urls', namespace='dialapp')),
url(r'^dialing/', include('dialing_app.urls', namespace='dialapp')),
url(r'^admin/', admin.site.urls) url(r'^admin/', admin.site.urls)
] ]

32
djing/utils/load_dot_from_nodeny.py

@ -1,32 +0,0 @@
#!/bin/env python3
# coding=utf-8
import os
import MySQLdb
from json import dumps
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djing.settings")
db = MySQLdb.connect(host="localhost", user="root", passwd="ps", db="nodeny", charset='utf8')
cursor = db.cursor()
result = dict()
# выбираем абонентов
sql = r"SELECT location, descr FROM places WHERE location LIKE 'Сад_%'"
cursor.execute(sql)
places = list()
res = cursor.fetchone()
while res:
places.append({
'loc': res[0],
'descr': res[1]
})
res = cursor.fetchone()
db.close()
f = open('../../places.json', 'w')
f.write(dumps(places, ensure_ascii=False).encode('utf8'))
f.close()

92
djing/utils/load_from_nodeny.py

@ -1,92 +0,0 @@
#!/bin/env python3
# coding=utf-8
import MySQLdb
from json import dumps, loads
def param_to_python(st):
st = st.replace('$VAR1 = ', '')
st = st.replace("'", '"')
st = st.replace(" =>", ':')
st = st.replace(";", '')
return loads(st)
def load_service(cursor, uid):
sql = "SELECT services.title, services.service_id, services.price, services.description, services.param " \
"FROM services LEFT JOIN users_services ON " \
"(users_services.service_id=services.service_id) WHERE users_services.uid=%d" % uid
cursor.execute(sql)
service_line = cursor.fetchone()
if service_line is not None:
service = {
'title': service_line[0],
'service_id': service_line[1],
'price': service_line[2],
'description': service_line[3],
'param': param_to_python(service_line[4])
}
else:
service = None
return service
def load_users(cursor, grp_id):
# выбираем абонентов
sql = r"SELECT users.name, users.fio, data0._adr_telefon, dictionary.v AS street, data0._adr_house, data0._birthday, " \
"users.grp, INET_NTOA(ip_pool.ip) AS ip, users.balance, AES_DECRYPT(users.passwd, 'Vu6saiZa') as decr_passwd, users.id, " \
"mac_uid.device_mac, mac_uid.device_port, mac_uid.oneconnect " \
"FROM users " \
"LEFT JOIN data0 ON (data0.uid = users.id) LEFT JOIN dictionary ON (dictionary.k = data0._adr_street AND dictionary.type = 'street') " \
"LEFT JOIN mac_uid ON (mac_uid.uid=users.id) " \
"LEFT JOIN ip_pool ON (ip_pool.uid = users.id) WHERE users.grp = %d" % grp_id
cursor.execute(sql)
users = [{
'name': res[0],
'fio': res[1],
'tel': res[2],
'street': str(res[3] or ''),
'house': str(res[4]),
'birth': res[5],
'grp': int(res[6]),
'ip': str(res[7] or ''),
'balance': float(res[8]),
'passw': res[9].decode("utf-8") if res[9] is not None else '',
'service': load_service(cursor, int(res[10])),
'opt82': {
'dev_mac': res[11],
'dev_port': res[12],
'oneconnect': res[13]
}
} for res in cursor.fetchall()]
return users
def load_groups(cursor):
# выбираем группы
sql = r'SELECT grp_id, grp_name FROM user_grp'
cursor.execute(sql)
groups = list()
for res in cursor.fetchall():
users = load_users(cursor=cursor, grp_id=int(res[0]))
groups.append({
'gid': int(res[0]),
'gname': res[1],
'users': users
})
return groups
if __name__ == "__main__":
db = MySQLdb.connect(host="127.0.0.1", user="user", passwd="password", db="db", charset='utf8')
cursor = db.cursor()
result = dict()
result = load_groups(cursor=cursor)
db.close()
f = open('dump.json', 'w')
f.write(dumps(result, ensure_ascii=False))
f.close()

45
djing/utils/push_snmp_passw.py

@ -1,45 +0,0 @@
# -*- coding: utf-8 -*-
import telnetlib
from mydefs import ping
from socket import error
from multiprocessing import Process
# Пробуем настроить свичи через telnet на snmp
def cmd(ip):
tn = telnetlib.Telnet(ip)
tn.read_until("login: ")
tn.write("\n")
tn.read_until("Password: ")
tn.write("\n")
tn.write("create snmp community ertNjuWr ReadWrite\n")
tn.write("save\n")
tn.write("save config\n")
tn.write("save config config_id 1\n")
tn.write("log\n")
print((tn.read_all()))
tn.close()
def prc(ip):
try:
if ping(ip):
cmd(ip)
except error:
print(('Error connect to', ip))
if __name__ == '__main__':
proc_list = list()
with open('swips.txt', 'r') as f:
for ln in f:
ip = ln.strip()
p = Process(target=prc, args=(ip,))
p.start()
proc_list.append(p)
for proc in proc_list:
proc.join()

29
djing/utils/save_dot_from_nodeny.py

@ -1,29 +0,0 @@
#!/bin/env python3
# coding=utf-8
import os
from json import load
import django
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djing.settings")
django.setup()
from devapp.models import Device
with open('../../places.json', 'r') as f:
dat = load(f)
for dt in dat:
if dt['descr']:
dt['descr']=dt['descr'].replace('10.15.', '10.115.')
dt['loc']=dt['loc'].encode('utf8')
try:
dev = Device.objects.get(ip_address=dt['descr'])
except Device.DoesNotExist:
dev = Device(
ip_address=dt['descr']
)
dev.comment=dt['loc']
dev.save()
print((dt['descr'], dt['loc'], dev))

280
djing/utils/save_from_nodeny.py

@ -1,280 +0,0 @@
#!/bin/env python3
# coding=utf-8
import os
from json import load
import django
from django.utils import timezone
from django.core.exceptions import ValidationError
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djing.settings")
django.setup()
from abonapp.models import Abon, AbonGroup, AbonRawPassword, AbonStreet, AbonTariff, Opt82
from ip_pool.models import IpPoolItem
from tariff_app.models import Tariff
class DumpService(object):
price = 0.0
speedIn = 0.0
speedOut = 0.0
def __init__(self, obj=None):
if obj is None: return
self.title = obj['title']
self.price = obj['price']
self.description = obj['description']
self.speedIn = int(obj['param']['speed_in1']) / 1000000
self.speedOut = int(obj['param']['speed_out1']) / 1000000
@staticmethod
def build_from_db(obj):
self = DumpService()
self.title = obj.title
self.price = obj.amount
self.description = obj.descr
self.speedIn = obj.speedIn
self.speedOut = obj.speedOut
return self
def __eq__(self, other):
assert isinstance(other, DumpService)
print('DBG:', type(other.price), other.price, type(self.price), self.price)
r = self.price == other.price
r = r and self.speedIn == other.speedIn
r = r and self.speedOut == other.speedOut
return r
def __ne__(self, other):
return not self.__eq__(other)
def __str__(self):
return "%s; '%.2f', %f %f" % (self.title, self.price, self.speedIn, self.speedOut)
class DumpAbon(object):
def __init__(self, obj=None):
if obj is None: return
self.name = obj['name']
self.fio = obj['fio']
self.tel = obj['tel']
self.street = obj['street']
self.house = obj['house']
self.birth = obj['birth']
self.grp = obj['grp']
self.ip = obj['ip'] if obj['ip'] != '' else None
self.balance = obj['balance']
self.passw = obj['passw']
if obj['opt82']['dev_mac'] is not None and obj['opt82']['dev_port'] is not None:
self.opt82 = {
'dev_mac': obj['opt82']['dev_mac'],
'dev_port': obj['opt82']['dev_port']
}
if obj['service'] is not None:
self.service = DumpService(obj['service'])
else:
self.service = None
@staticmethod
def build_from_django(obj):
assert isinstance(obj, Abon)
self = DumpAbon()
self.name = obj.username
self.fio = obj.fio
self.tel = obj.telephone
self.street = obj.street
self.house = obj.house
self.birth = obj.birth_day
if obj.group is None:
self.grp = None
else:
self.grp = obj.group.pk
if obj.ip_address is None:
self.ip = None
else:
self.ip = obj.ip_address
self.balance = obj.ballance
try:
raw_passw = AbonRawPassword.objects.get(account=obj)
except AbonRawPassword.DoesNotExist:
raw_passw = ''
self.passw = raw_passw
srv = obj.active_tariff()
if srv is not None:
self.service = DumpService.build_from_db(srv.tariff)
else:
self.service = None
if obj.opt82 is not None and obj.opt82.mac is not None and obj.opt82.port is not None:
self.opt82 = {
'dev_mac': obj.opt82.mac,
'dev_port': obj.opt82.port
}
return self
def __eq__(self, other):
assert isinstance(other, DumpAbon)
r = self.name == other.name
r = r and self.name == other.name
r = r and self.fio == other.fio
r = r and self.tel == other.tel
r = r and self.street == other.street
r = r and self.house == other.house
r = r and self.birth == other.birth
r = r and self.grp == other.grp
r = r and self.ip == other.ip
r = r and self.balance == other.ballance
return r
def __ne__(self, other):
return not self.__eq__(other)
def add_service_if_not_exist(service):
assert isinstance(service, DumpService)
try:
obj = Tariff.objects.get(speedIn=service.speedIn, speedOut=service.speedOut, amount=service.price)
except Tariff.DoesNotExist:
obj = Tariff.objects.create(
title=service.title,
descr=service.description,
speedIn=service.speedIn,
speedOut=service.speedOut,
amount=service.price,
calc_type='Dp'
)
return obj
def add_raw_password_if_not_exist(acc, raw_passw):
try:
psw = AbonRawPassword.objects.get(account=acc)
#if psw != raw_passw:
# psw.passw_text = raw_passw
# psw.save(update_fields=['passw_text'])
except AbonRawPassword.DoesNotExist:
psw = AbonRawPassword.objects.create(account=acc, passw_text=raw_passw)
return psw
def add_opt82_if_not_exist(mac, port):
print(mac, port)
try:
opt82 = Opt82.objects.get(mac=mac, port=port)
except Opt82.DoesNotExist:
opt82 = Opt82.objects.create(mac=mac, port=port)
return opt82
def load_users(obj, group):
if len(obj) < 1:
return
for usr in obj:
# абонент из дампа
dump_abon = DumpAbon(usr)
# абонент из биллинга
print('\t', dump_abon.name, dump_abon.fio, dump_abon.ip)
try:
abon = Abon.objects.get(username=dump_abon.name)
bl_abon = DumpAbon.build_from_django(abon)
if bl_abon != dump_abon:
update_user(abon, dump_abon, group)
except Abon.DoesNotExist:
# добавляем абонента
abon = add_user(dump_abon, group)
if abon is None:
raise Exception("Чё за херня!? Не создался абонент")
abon_service_from_dump = dump_abon.service
if abon_service_from_dump is None:
continue
abon_service = add_service_if_not_exist(abon_service_from_dump)
try:
AbonTariff.objects.get(abon=abon, tariff=abon_service)
except AbonTariff.DoesNotExist:
calc_obj = abon_service.get_calc_type()(abon_service)
AbonTariff.objects.create(
abon=abon,
tariff=abon_service,
time_start=timezone.now(),
deadline=calc_obj.calc_deadline()
)
try:
if hasattr(dump_abon, 'opt82'):
abon.opt82 = add_opt82_if_not_exist(dump_abon.opt82['dev_mac'], dump_abon.opt82['dev_port'])
abon.save(update_fields=['opt82'])
except ValidationError as e:
print('\t', e)
def add_user(obj, user_group):
assert isinstance(obj, DumpAbon)
street = None
ip = None
try:
if obj.ip is not None:
ip = IpPoolItem.objects.get(ip=obj.ip)
street = AbonStreet.objects.get(name=obj.street, group=user_group)
except IpPoolItem.DoesNotExist:
if obj.ip is not None:
ip = IpPoolItem.objects.create(ip=obj.ip)
except AbonStreet.DoesNotExist:
street = AbonStreet.objects.create(name=obj.street, group=user_group)
abon = Abon()
abon.username = obj.name
abon.fio = obj.fio
abon.telephone = obj.tel
abon.street = street
abon.house = obj.house
abon.birth_day = obj.birth
abon.group = user_group
abon.ip_address = ip
abon.ballance = obj.balance
abon.set_password(obj.passw)
abon.save()
add_raw_password_if_not_exist(abon, obj.passw)
return abon
def update_user(db_abon, obj, user_group):
assert isinstance(obj, DumpAbon)
assert isinstance(db_abon, Abon)
street = None
ip = None
try:
if obj.ip is not None:
ip = IpPoolItem.objects.get(ip=obj.ip)
street = AbonStreet.objects.get(name=obj.street, group=user_group)
except IpPoolItem.DoesNotExist:
if obj.ip is not None:
ip = IpPoolItem.objects.create(ip=obj.ip)
except AbonStreet.DoesNotExist:
street = AbonStreet.objects.create(name=obj.street, group=user_group)
db_abon.fio = obj.fio
db_abon.telephone = obj.tel
db_abon.street = street
db_abon.house = obj.house
#db_abon.birth_day = datetime(obj.birth)
db_abon.group = user_group
db_abon.ip_address = ip
db_abon.ballance = obj.balance
db_abon.set_password(obj.passw)
db_abon.save()
add_raw_password_if_not_exist(db_abon, obj.passw)
if __name__ == "__main__":
with open('dump.json', 'r') as f:
dat = load(f)
for grp in dat:
try:
abgrp=AbonGroup.objects.get(title=grp['gname'])
except AbonGroup.DoesNotExist:
abgrp = AbonGroup.objects.create(
title=grp['gname']
)
print(grp['gname'])
load_users(grp['users'], abgrp)

80
docs/install.md

@ -0,0 +1,80 @@
## Установка(не завершил описание):
Работа предполагается на python3.
Я предпочитаю запускать wsgi сервер на связке uWSGI + Nginx, так что ставить будем соответствующие пакеты.
На ArchLinux нужые пакеты можно установить так:
```
# pacman -Sy mariadb-clients python3 python-pip nginx uwsgi
```
Дальше ставим всё для python через pip:
```
# pip install git+https://github.com/nerosketch/djing.git
```
### Настройка WEB Сервера
Условимся что путь к папке с проектом находится по адресу: </var/www/djing>
Конфиг Nginx на моём рабочем сервере выглядит так:
user http;
worker_processes auto;
pid /run/nginx.pid;
events {
worker_connections 1024;
}
http {
sendfile on;
upstream djing { server unix:///run/uwsgi/djing.sock; }
server {
listen 80;
server_name <ваш-домен>.com;
root /var/www/djing;
charset utf-8;
# укажите где лежит ваш раздел с медиа для сайта
location /media {
alias /var/www/djing/media;
}
# местоположение статики
location /static {
alias /var/www/djing/static;
}
# тут надо указать путь куда у вас установился Django + путь к статике админки
# путь к Django тут: /usr/lib/python3.5/site-packages/django
# путь к статике соответственно: contrib/admin/static/admin
location /static/admin {
alias /usr/lib/python3.5/site-packages/django/contrib/admin/static/admin;
}
# на корневом url / реагируем с помощью сокета проекта
# у нас он называется "djing": upstream djing { server ...
location / {
uwsgi_pass djing;
include uwsgi_params;
}
}
}
Это минимальный конфиг Nginx для работы. Проверте файл /run/uwsgi/djing.sock на доступность пользователю http для чтения.
Далее настраиваем uWSGI. Мой конфиг для uWSGI в режиме emperor:
[uwsgi]
uid = http
gid = http
pidfile = /run/uwsgi/uwsgi.pid
emperor = /etc/uwsgi.d
stats = /run/uwsgi/stats.sock
chmod-socket = 660
emperor-tyrant = true
cap = setgid,setuid
У меня конфиг лежит по адресу /etc/uwsgi.ini
### Настраиваем системные утилиты
Если ваша система работает с поддержкой *systemd* то в каталоге *systemd_units* проекта вы найдёте юниты для systemd.
Скопируйте их в каталог юнитов systemd

7
docs/intro.md

@ -0,0 +1,7 @@
Текущие возможности:
1. Может наблюдать за устройствами по snmp
2. Отправлять изменения мгновенно на mikrotik
3. Привязывать на карте к точкам топологии устройства
4. Есть привязка монтажника к группам абонентов
5. Есть менеджер задач на абонентов. Это оператор может выбрать абонента и описать проблему. Система отправит оповещение через telegram ответственному за групу указанного абонента монтажнику с текстом проблемы, адресом и телефоном абонента.
6. Долгие или сложные задачи можно отправлять на очередь исполнения

2
mapapp/templates/maps/map_tooltip.html

@ -1,7 +1,7 @@
<h4>{{ dot.title }}</h4> <h4>{{ dot.title }}</h4>
<div class="list-group"> <div class="list-group">
{% for dev in devs %} {% for dev in devs %}
<a class="list-group-item" href="{% url 'devapp:view' dev.id %}" target="_blank">
<a class="list-group-item" href="{% url 'devapp:view' dev.user_group.pk dev.pk %}" target="_blank">
{{ dev.comment }} {{ dev.comment }}
</a> </a>
{% empty %} {% empty %}

7
photo_app/models.py

@ -27,9 +27,7 @@ class Photo(models.Model):
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if not self.image: if not self.image:
return return
super().save()
super(Photo, self).save()
im = Image.open(self.image.path) im = Image.open(self.image.path)
im.thumbnail((759, 759), Image.ANTIALIAS) im.thumbnail((759, 759), Image.ANTIALIAS)
@ -44,8 +42,7 @@ class Photo(models.Model):
im.save(fname) im.save(fname)
os.remove(self.image.path) os.remove(self.image.path)
self.image = "%s.%s" % (hs, ext) self.image = "%s.%s" % (hs, ext)
super().save()
super(Photo, self).save()
# class Meta: # class Meta:
# unique_together = (('image',),) # unique_together = (('image',),)

8
queue_mngr.py

@ -2,6 +2,7 @@
import os import os
import sys import sys
from rq import Connection, Worker from rq import Connection, Worker
from pid.decorator import pidfile
import django import django
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djing.settings") os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djing.settings")
from agent import NasFailedResult, NasNetworkError from agent import NasFailedResult, NasNetworkError
@ -11,7 +12,8 @@ from django.core.exceptions import ValidationError
""" """
Заустить этот скрипт как демон, он соединяет redis и django Заустить этот скрипт как демон, он соединяет redis и django
""" """
if __name__ == '__main__':
@pidfile()
def main():
try: try:
django.setup() django.setup()
with Connection(): with Connection():
@ -22,3 +24,7 @@ if __name__ == '__main__':
print('NAS:', e) print('NAS:', e)
except (ValidationError, ValueError) as e: except (ValidationError, ValueError) as e:
print('ERROR:', e) print('ERROR:', e)
if __name__ == "__main__":
main()

1
requirements.txt

@ -8,3 +8,4 @@ xmltodict
mysqlclient mysqlclient
easysnmp easysnmp
rq rq
pid

2
searchapp/views.py

@ -4,12 +4,14 @@ from django.shortcuts import render
from django.utils.html import escape from django.utils.html import escape
from abonapp.models import Abon from abonapp.models import Abon
from mydefs import ip_addr_regex from mydefs import ip_addr_regex
from django.contrib.auth.decorators import login_required
def replace_without_case(orig, old, new): def replace_without_case(orig, old, new):
return re.sub(old, new, orig, flags=re.IGNORECASE) return re.sub(old, new, orig, flags=re.IGNORECASE)
@login_required
def home(request): def home(request):
s = request.GET.get('s') s = request.GET.get('s')
s = s.replace('+', '') s = s.replace('+', '')

11
static/css/custom.css

@ -138,6 +138,7 @@ td.btn-group {
border-radius: 3px; border-radius: 3px;
padding: 6px; padding: 6px;
width: 79px; width: 79px;
font-size: x-small;
} }
.port .port-img{ .port .port-img{
background-image: url(/static/img/icon-port-64x64-grey.png); background-image: url(/static/img/icon-port-64x64-grey.png);
@ -155,6 +156,9 @@ a.port-img{
text-decoration: none; text-decoration: none;
} }
/* 10GBit/s */
.port.ten{background-color: #a4fff7;}
/* 1GBit/s */ /* 1GBit/s */
.port.giga{background-color: #caccfb;} .port.giga{background-color: #caccfb;}
@ -242,8 +246,7 @@ button[data-toggle=offcanvas]{
fill-opacity: 0.3; fill-opacity: 0.3;
} }
a.navbar-brand {
padding-left: 88px;
}
/*
* Сужаем аудио элемент чтоб скрыть большинство контролов
*/
audio{width: 100px;}

70
static/js/my.js

@ -27,7 +27,7 @@ $(document).ajaxError(function (ev, jqXHR, ajaxSettings, thrownError) {
$.fn.selectajax = function (opt) { $.fn.selectajax = function (opt) {
var settings = $.extend( { var settings = $.extend( {
url : '/api'
url : this.attr('data-dst')
}, opt); }, opt);
var selectbtn = this.children('button.selectajax-btn'); var selectbtn = this.children('button.selectajax-btn');
@ -40,7 +40,6 @@ $(document).ajaxError(function (ev, jqXHR, ajaxSettings, thrownError) {
var hr = a.attr('href'); var hr = a.attr('href');
var tx = a.text(); var tx = a.text();
selecthid.val(hr.substr(1)); selecthid.val(hr.substr(1));
console.debug(tx);
selectbtn.text(tx).removeClass('hidden'); selectbtn.text(tx).removeClass('hidden');
selectinp.addClass('hidden').val(tx); selectinp.addClass('hidden').val(tx);
}; };
@ -49,7 +48,7 @@ $(document).ajaxError(function (ev, jqXHR, ajaxSettings, thrownError) {
$.getJSON(settings.url, {'s': this.value}, function (r) { $.getJSON(settings.url, {'s': this.value}, function (r) {
selectul.empty(); selectul.empty();
r.forEach(function (o) { r.forEach(function (o) {
var li = $('<li><a href="#' + o.id + '">' + o.name + ": " + o.fio + '</a></li>');
var li = $('<li><a href="#' + o.id + '">' + o.text + '</a></li>');
selectul.append(li); selectul.append(li);
li.on('click', selectajax_click) li.on('click', selectajax_click)
}); });
@ -70,17 +69,56 @@ $(document).ajaxError(function (ev, jqXHR, ajaxSettings, thrownError) {
})(jQuery); })(jQuery);
$(document).ready(function () {
// AudioPlayer
(function ($) {
$.fn.aplayer = function(){
// ajax tabs
$('.nav-tabs a').on('show.bs.tab', function (e) {
var ct = $(e.target).attr('href');
var remoteUrl = $(this).attr('data-tab-remote');
if (remoteUrl !== '') {
$(ct).load(remoteUrl);
}
var def_play = function(e){
var audiotag = e.data['audiotag'][0];
if(audiotag.readyState == 0){
$(this).prop('disabled', true);
return;
}else
$(this).prop('disabled', false);
if(audiotag.paused)
audiotag.play();
else
audiotag.pause();
};
var def_canplay = function(){
var els = $(this).parent();
els.prop('disabled', false).removeClass('disabled');
els.siblings().prop('disabled', false).removeClass('disabled');
};
var def_on_play = function(){
$(this).siblings('span.glyphicon').attr('class', 'glyphicon glyphicon-pause');
};
var def_on_pause = function(){
$(this).siblings('span.glyphicon').attr('class', 'glyphicon glyphicon-play');
};
this.each(function(){
var i = $(this);
var audiotag = i.children('audio');
var icon = i.children('span.glyphicon');
i.on('click', {'audiotag': audiotag}, def_play);
audiotag.on('canplay', def_canplay);
audiotag.on('play', def_on_play);
audiotag.on('pause', def_on_pause);
}); });
};
})(jQuery);
$(document).ready(function () {
// Live html5 image preview // Live html5 image preview
if (window.File && window.FileReader && window.FileList && window.Blob) { if (window.File && window.FileReader && window.FileList && window.Blob) {
$('input[type=file].live_review').on('change', function () { $('input[type=file].live_review').on('change', function () {
@ -114,9 +152,7 @@ $(document).ready(function () {
}); });
$('div.selectajax').selectajax({
url: '/abons/api/abon_filter'
});
$('div.selectajax').selectajax();
$('[data-toggle=offcanvas]').click(function () { $('[data-toggle=offcanvas]').click(function () {
$('.row-offcanvas').toggleClass('active'); $('.row-offcanvas').toggleClass('active');
@ -147,6 +183,10 @@ $(document).ready(function () {
self.html(r.dat); self.html(r.dat);
}); });
return false; return false;
})
});
$('button.player-btn').aplayer();
$('[data-toggle="tooltip"]').tooltip({container:'body'});
}); });

18
statistics/migrations/0002_delete_statelem.py

@ -1,18 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2017-04-25 13:27
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('statistics', '0001_initial'),
]
operations = [
migrations.DeleteModel(
name='StatElem',
),
]

34
statistics/migrations/0002_statcache.py

@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2017-05-31 17:37
from __future__ import unicode_literals
from django.db import migrations, models
import mydefs
import statistics.fields
class Migration(migrations.Migration):
initial = True
dependencies = [
('statistics', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='StatCache',
fields=[
('last_time', statistics.fields.UnixDateTimeField()),
('ip', mydefs.MyGenericIPAddressField(max_length=8, primary_key=True, protocol='ipv4', serialize=False)),
('octets', models.PositiveIntegerField(default=0)),
('packets', models.PositiveIntegerField(default=0)),
],
options={
'db_table': 'flowcache',
},
),
migrations.DeleteModel(
name='StatElem',
),
]

64
statistics/models.py

@ -1,25 +1,19 @@
import math import math
from datetime import datetime, timedelta, date, time from datetime import datetime, timedelta, date, time
from django.db import models, ProgrammingError, connection
from django.db import models, connection
from django.utils.timezone import now
from mydefs import MyGenericIPAddressField from mydefs import MyGenericIPAddressField
from .fields import UnixDateTimeField from .fields import UnixDateTimeField
from mydefs import LogicError
class StatManager(models.Manager):
def get_dates():
tables = connection.introspection.table_names()
tables = [t.replace('flowstat_', '') for t in tables if t.startswith('flowstat_')]
return [datetime.strptime(t, '%d%m%Y').date() for t in tables]
def traffic_by_ip(self, ip):
try:
traf = self.order_by('-cur_time').filter(ip=ip, octets__gt=524288)[0]
now = datetime.now()
if traf.cur_time < now - timedelta(minutes=55):
return False, traf
else:
return True, traf
except IndexError:
return False, None
except ProgrammingError as e:
raise LogicError(e)
class StatManager(models.Manager):
def chart(self, ip_addr, count_of_parts=12, want_date=date.today()): def chart(self, ip_addr, count_of_parts=12, want_date=date.today()):
def byte_to_mbit(x): def byte_to_mbit(x):
@ -27,6 +21,8 @@ class StatManager(models.Manager):
def split_list(lst, chunk_count): def split_list(lst, chunk_count):
chunk_size = len(lst) // chunk_count chunk_size = len(lst) // chunk_count
if chunk_size == 0:
chunk_size = 1
return [lst[i:i+chunk_size] for i in range(0, len(lst), chunk_size)] return [lst[i:i+chunk_size] for i in range(0, len(lst), chunk_size)]
def avarage(elements): def avarage(elements):
@ -42,7 +38,7 @@ class StatManager(models.Manager):
charts_times = split_list(charts_times, count_of_parts) charts_times = split_list(charts_times, count_of_parts)
charts_times = [avarage(t) for t in charts_times] charts_times = [avarage(t) for t in charts_times]
charts_data = map(lambda x, y: (x, y), charts_times, charts_octets)
charts_data = zip(charts_times, charts_octets)
charts_data = ["{x: new Date(%d), y: %.2f}" % (cd[0], cd[1]) for cd in charts_data] charts_data = ["{x: new Date(%d), y: %.2f}" % (cd[0], cd[1]) for cd in charts_data]
midnight = datetime.combine(want_date, time.min) midnight = datetime.combine(want_date, time.min)
charts_data.append("{x:new Date(%d),y:0}" % (int(charts_times[-1:][0]) + 1)) charts_data.append("{x:new Date(%d),y:0}" % (int(charts_times[-1:][0]) + 1))
@ -51,11 +47,6 @@ class StatManager(models.Manager):
else: else:
return return
def get_dates(self):
tables = connection.introspection.table_names()
tables = [t.replace('flowstat_', '') for t in tables if t.startswith('flowstat_')]
return [datetime.strptime(t, '%d%m%Y').date() for t in tables]
class StatElem(models.Model): class StatElem(models.Model):
cur_time = UnixDateTimeField(primary_key=True) cur_time = UnixDateTimeField(primary_key=True)
@ -65,11 +56,19 @@ class StatElem(models.Model):
objects = StatManager() objects = StatManager()
# ReadOnly
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
return
pass
# ReadOnly
def delete(self, *args, **kwargs): def delete(self, *args, **kwargs):
return
pass
def delete_month(self):
cursor = connection.cursor()
table_name = self._meta.db_table
sql = "DROP TABLE %s;" % table_name
cursor.execute(sql)
@staticmethod @staticmethod
def percentile(N, percent, key=lambda x:x): def percentile(N, percent, key=lambda x:x):
@ -97,10 +96,27 @@ class StatElem(models.Model):
abstract = True abstract = True
def getModel(want_date=datetime.now()):
def getModel(want_date=now()):
class DynamicStatElem(StatElem): class DynamicStatElem(StatElem):
class Meta: class Meta:
db_table = 'flowstat_%s' % want_date.strftime("%d%m%Y") db_table = 'flowstat_%s' % want_date.strftime("%d%m%Y")
abstract = False abstract = False
return DynamicStatElem return DynamicStatElem
class StatCache(models.Model):
last_time = UnixDateTimeField()
ip = MyGenericIPAddressField(primary_key=True)
octets = models.PositiveIntegerField(default=0)
packets = models.PositiveIntegerField(default=0)
def is_online(self):
return self.last_time > now() - timedelta(minutes=55)
def is_today(self):
return date.today() == self.last_time.date()
class Meta:
db_table = 'flowcache'

15
systemd_units/djing_queue.service

@ -0,0 +1,15 @@
[Unit]
Description=Djing queue manager
[Service]
Type=simple
ExecStart=/usr/bin/python3 ./queue_mngr.py > /dev/null
PIDFile=/run/queue_mngr.py.pid
WorkingDirectory=/var/www/djing
TimeoutSec=15
Restart=always
User=http
Group=http
[Install]
WantedBy=multi-user.target

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save