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. 72
      abonapp/templates/abonapp/buy_tariff.html
  14. 75
      abonapp/templates/abonapp/editAbon.html
  15. 38
      abonapp/templates/abonapp/modal_addstreet.html
  16. 6
      abonapp/templates/abonapp/modal_dev.html
  17. 38
      abonapp/templates/abonapp/modal_editstreet.html
  18. 35
      abonapp/templates/abonapp/peoples.html
  19. 113
      abonapp/templates/abonapp/service.html
  20. 9
      abonapp/urls_abon.py
  21. 204
      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. 39
      accounts_app/views.py
  27. 44
      agent/commands/dhcp.py
  28. 31
      agent/core.py
  29. 140
      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. 134
      clientsideapp/templates/clientsideapp/services.html
  43. 3
      clientsideapp/urls.py
  44. 116
      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. 258
      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. 72
      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 должно быть настроено точно.
Таблицу кеша статистики лучше сделать в памяти т.к. будет часто обновляться
ALTER TABLE flowcache ENGINE=MEMORY;

17
README.md

@ -2,19 +2,6 @@
Попытка интернет биллинга. djing сокращение от django-billing. Это web интерфейс управления абонентами интернет-провайдера.
Сейчас идёт тестирвоание работы на Mikrotik, функционал пока минимальный, т.к. пишу в свободное время. Работа планируется в реальных условиях и на реальных абонентах.
Использовано 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.ExtraFieldsModel)
admin.site.register(models.AllPayLog)
admin.site.register(models.Opt82)
admin.site.register(models.PassportInfo)
admin.site.register(models.AbonDevice)

25
abonapp/forms.py

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
from django.http import QueryDict
from datetime import datetime
from django.utils.translation import ugettext as _
from django import forms
@ -7,7 +6,6 @@ from django.contrib.auth.hashers import make_password
from random import choice
from string import digits, ascii_lowercase
from . import models
from .formfields import MACAddressField
def generate_random_username(length=6, chars=digits, split=2, delimiter=''):
@ -88,19 +86,6 @@ class AbonForm(forms.ModelForm):
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 Meta:
model = models.AbonGroup
@ -136,3 +121,13 @@ class ExtraFieldForm(forms.ModelForm):
'field_type': forms.Select(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 ""
"Project-Id-Version: PACKAGE VERSION\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"
"Last-Translator: Dmitry Novikov nerosketch@gmail.com\n"
"Language: ru\n"
@ -20,28 +20,26 @@ msgstr ""
"%100>=11 && n%100<=14)? 2 : 3);\n"
#: abonapp/formfields.py:12
#, fuzzy
#| msgid "Enter a valid MAC Address."
msgid "Enter a valid integer."
msgstr "Введите валидный mac адрес"
msgstr "Введите верное число"
#: abonapp/formfields.py:21
msgid "Enter a valid MAC Address."
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/viewAbon.html:28
msgid "login"
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
msgid "fio"
msgstr "ФИО"
#: abonapp/forms.py:61
#: abonapp/forms.py:60
msgid "telephone placeholder"
msgstr "+[7,8,9,3] и 10,11 цифр"
@ -69,7 +67,7 @@ msgstr "Текстовое поле"
msgid "Floating field"
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
msgid "Ip Address"
msgstr "IP Адрес"
@ -78,23 +76,26 @@ msgstr "IP Адрес"
msgid "Double invalid value"
msgstr "Введите число с плавающей запятой"
#: abonapp/models.py:243
#: abonapp/models.py:244
msgid "Buy service perm"
msgstr "Покупка тарифа абоненту"
#: abonapp/models.py:244
#: abonapp/models.py:245
msgid "Can view passport"
msgstr "Может просматривать паспортные данные"
#: abonapp/models.py:288
#: abonapp/models.py:292
msgid "Buy service default log"
msgstr "Покупка тарифного плана через админку"
#: abonapp/models.py:346
#: abonapp/models.py:307
msgid "service overdue log"
msgstr "Услуга просрочена, отключаем, и подключаем новую"
#: abonapp/models.py:347
msgid "Ip address already exist"
msgstr "Такой ip уже у кого-то есть"
#: abonapp/templates/abonapp/activate_service.html:8
#: abonapp/templates/abonapp/addAbon.html:7
#: abonapp/templates/abonapp/addGroup.html:7
#: abonapp/templates/abonapp/addInvoice.html:7
@ -108,42 +109,22 @@ msgstr "Такой ip уже у кого-то есть"
#: abonapp/templates/abonapp/invoiceForPayment.html:7
#: abonapp/templates/abonapp/log.html:7
#: abonapp/templates/abonapp/peoples.html:8
#: abonapp/templates/abonapp/peoples.html:127
#: abonapp/templates/abonapp/peoples.html:132
msgid "User groups"
msgstr "Группы абонентов"
#: abonapp/templates/abonapp/activate_service.html:11
#: abonapp/templates/abonapp/activate_service.html:18
#: abonapp/templates/abonapp/services.html:44
msgid "Activate service"
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/addGroup.html:29
#: abonapp/templates/abonapp/addInvoice.html:46
#: abonapp/templates/abonapp/buy_tariff.html:57
#: 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/modal_dev.html:29
#: abonapp/templates/abonapp/passport_view.html:49
@ -152,8 +133,8 @@ msgstr "Сохранить"
#: abonapp/templates/abonapp/addAbon.html:9
#: 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"
msgstr "Добавить абонента"
@ -163,13 +144,13 @@ msgstr "Фамилия и Имя"
#: abonapp/templates/abonapp/addAbon.html:37
#: abonapp/templates/abonapp/editAbon.html:29
#: abonapp/templates/abonapp/peoples.html:52
#: abonapp/templates/abonapp/peoples.html:53
#: abonapp/templates/abonapp/viewAbon.html:36
msgid "Telephone"
msgstr "Телефон"
#: abonapp/templates/abonapp/addAbon.html:45
#: abonapp/templates/abonapp/editAbon.html:66
#: abonapp/templates/abonapp/editAbon.html:73
#: abonapp/templates/abonapp/viewAbon.html:16
msgid "User group"
msgstr "Группа"
@ -177,7 +158,7 @@ msgstr "Группа"
#: abonapp/templates/abonapp/addAbon.html:53
#: abonapp/templates/abonapp/addInvoice.html:40
#: 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/log.html:20
#: abonapp/templates/abonapp/payHistory.html:12
@ -186,19 +167,19 @@ msgid "Comment"
msgstr "Комментарий"
#: 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
msgid "Street"
msgstr "Улица"
#: abonapp/templates/abonapp/addAbon.html:67
#: abonapp/templates/abonapp/peoples.html:48
#: abonapp/templates/abonapp/peoples.html:49
msgid "Apartment"
msgstr "Квартира"
#: abonapp/templates/abonapp/addAbon.html:76
#: abonapp/templates/abonapp/editAbon.html:73
#: abonapp/templates/abonapp/editAbon.html:80
#: abonapp/templates/abonapp/viewAbon.html:54
msgid "Password"
msgstr "Пароль"
@ -257,7 +238,7 @@ msgstr "Купить новую услугу (заказать тариф) дл
#: abonapp/templates/abonapp/debtors.html:20
#: abonapp/templates/abonapp/log.html:19
#: abonapp/templates/abonapp/payHistory.html:8
#: abonapp/templates/abonapp/peoples.html:23
#: abonapp/templates/abonapp/peoples.html:24
msgid "Sub"
msgstr "Абонент"
@ -266,6 +247,19 @@ msgstr "Абонент"
msgid "currency"
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:17
#: abonapp/templates/abonapp/services.html:72
@ -327,15 +321,54 @@ msgstr "Автор"
msgid "Debts not found"
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
msgid "Change subscriber"
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:29
#: abonapp/templates/abonapp/viewAbon.html:33
@ -346,55 +379,54 @@ msgstr "Изменение абонента"
msgid "Not assigned"
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
msgid "House"
msgstr "Дом"
#: abonapp/templates/abonapp/editAbon.html:59
#: abonapp/templates/abonapp/editAbon.html:66
#: abonapp/templates/abonapp/viewAbon.html:22
msgid "Is active"
msgstr "Активен"
#: abonapp/templates/abonapp/editAbon.html:99
#: abonapp/templates/abonapp/editAbon.html:106
msgid "Send account info to user"
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
#| msgid "Add debt"
msgid "Add new task"
msgstr "Добавить квитанцию"
#: abonapp/templates/abonapp/editAbon.html:116
#: abonapp/templates/abonapp/editAbon.html:123
msgid "Remove clutch"
msgstr "Удалить муфту"
#: abonapp/templates/abonapp/editAbon.html:120
#: abonapp/templates/abonapp/editAbon.html:127
#: abonapp/templates/abonapp/modal_dev.html:6
msgid "Add clutch"
msgstr "Добавить муфту"
#: abonapp/templates/abonapp/editAbon.html:143
#: abonapp/templates/abonapp/editAbon.html:170
msgid "DHCP information"
msgstr "DHCP информация"
#: abonapp/templates/abonapp/editAbon.html:149
#: abonapp/templates/abonapp/editAbon.html:176
msgid "Mac Address"
msgstr "Мак"
#: abonapp/templates/abonapp/editAbon.html:156
#: abonapp/templates/abonapp/editAbon.html:183
msgid "Port"
msgstr "Порт"
#: abonapp/templates/abonapp/editAbon.html:167
#: abonapp/templates/abonapp/editAbon.html:194
msgid "Reset 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"
msgstr "Удалить"
@ -501,7 +533,7 @@ msgid "Refill"
msgstr "Пополнить"
#: abonapp/templates/abonapp/modal_dev.html:13
msgid "Select a device"
msgid "Select the device"
msgstr "Выберите устройство"
#: abonapp/templates/abonapp/modal_extra_field.html:11
@ -551,44 +583,76 @@ msgstr "Пополнить счёт"
msgid "The people in the selected group"
msgstr "Народ в выбранной группе"
#: abonapp/templates/abonapp/peoples.html:27
#: abonapp/templates/abonapp/peoples.html:28
msgid "Last traffic"
msgstr "Последний траффик"
msgstr "Траф."
#: abonapp/templates/abonapp/peoples.html:30
#: abonapp/templates/abonapp/peoples.html:31
#, fuzzy
#| msgid "Ip Address"
msgid "Ip address"
msgstr "IP Адрес"
#: abonapp/templates/abonapp/peoples.html:53
#: abonapp/templates/abonapp/peoples.html:54
#: abonapp/templates/abonapp/services.html:10
msgid "Service"
msgstr "Услуга"
#: abonapp/templates/abonapp/peoples.html:56
#: abonapp/templates/abonapp/peoples.html:57
msgid "Ballance"
msgstr "Балланс"
#: abonapp/templates/abonapp/peoples.html:108
#: abonapp/templates/abonapp/peoples.html:113
msgid "Subscribers not found"
msgstr "Абоненты не найдены"
#: abonapp/templates/abonapp/peoples.html:125
#: abonapp/templates/abonapp/peoples.html:130
msgid "Refresh subscribers on NAS"
msgstr "Обновить абонентов в NAS"
#: abonapp/templates/abonapp/peoples.html:128
#: abonapp/templates/abonapp/peoples.html:133
msgid "Tariffs in groups"
msgstr "Тарифы в группах"
#: abonapp/templates/abonapp/peoples.html:141
#: abonapp/templates/abonapp/peoples.html:148
msgid "No streets found for that group"
msgstr "Не найдены улицы для группы"
#: 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
msgid "Input speed"
@ -646,241 +710,241 @@ msgstr "Просмотр абонента"
msgid "yes,no"
msgstr "Да,Нет"
#: abonapp/views.py:74
#: abonapp/views.py:75
msgid "create group success msg"
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"
msgstr "Некоторые поля заполнены не правильно, проверте ещё раз"
#: abonapp/views.py:113 abonapp/views.py:176
#: abonapp/views.py:114 abonapp/views.py:177
msgid "delete group success msg"
msgstr "Группа успешно удалена"
#: abonapp/views.py:134
#: abonapp/views.py:135
msgid "create abon success msg"
msgstr "Абонент успешно создан"
#: abonapp/views.py:148
#: abonapp/views.py:149
msgid "Address"
msgstr "Адрес"
#: abonapp/views.py:170
#: abonapp/views.py:171
msgid "delete abon success msg"
msgstr "Абонент успешно удалён"
#: abonapp/views.py:179
#: abonapp/views.py:180
msgid "I not know what to delete"
msgstr "Не понятно что удалять"
#: abonapp/views.py:183
#: abonapp/views.py:184
#, python-format
msgid "NAS says: '%s'"
msgstr "NAS сказал: '%s'"
#: abonapp/views.py:199
#: abonapp/views.py:201
msgid "fill account through admin side"
msgstr "Пополнение счёта через админку"
#: abonapp/views.py:201
#: abonapp/views.py:203
#, python-format
msgid "Account filled successfully on %.2f"
msgstr ""
#: abonapp/views.py:204
#: abonapp/views.py:206
msgid "I not know the account id"
msgstr "Счёт успешно пополнен на %.2f"
#: abonapp/views.py:280
#: abonapp/views.py:282
msgid "edit abon success msg"
msgstr "Абонент успешно изменён"
#: abonapp/views.py:295
#: abonapp/views.py:297
msgid "User has not have password, and cannot login"
msgstr "Для абонента не задан пароль, он не сможет войти в учётку"
#: abonapp/views.py:297 abonapp/views.py:692
#: abonapp/views.py:299 abonapp/views.py:703
msgid "User device was not found"
msgstr "Пользовательское устройство не найдено"
#: abonapp/views.py:350
#: abonapp/views.py:355
#, fuzzy
#| msgid "Abon does not exist"
msgid "User does not exist"
msgstr "Абонент не найден"
#: abonapp/views.py:383
#: abonapp/views.py:388
msgid "Receipt has been created"
msgstr "Квитанция на оплату была создана"
#: abonapp/views.py:413
#: abonapp/views.py:419
msgid "Tariff has been picked"
msgstr "Тариф успешно выбран"
#: abonapp/views.py:421
#: abonapp/views.py:427
msgid "Tariff your picked does not exist"
msgstr "Тариф, который вы выбрали, не существует"
#: abonapp/views.py:484
#: abonapp/views.py:491
msgid "Refunds for unused resources"
msgstr "Возврат средств за неиспользованные ресурсы"
#: abonapp/views.py:490
#: abonapp/views.py:497
msgid "Service has been finished successfully"
msgstr "Услуга успешно завершена"
#: abonapp/views.py:493 abonapp/views.py:525
#: abonapp/views.py:500 abonapp/views.py:533
msgid "Not confirmed"
msgstr "Действие не подтверждено"
#: abonapp/views.py:528
#: abonapp/views.py:536
msgid "Service has been activated successfully"
msgstr "Услуга успешно активирована"
#: abonapp/views.py:554
#: abonapp/views.py:562
msgid "User has been detached from service"
msgstr "Абонент отвязан от услуги"
#: abonapp/views.py:634
#: abonapp/views.py:645
msgid "Passport information has been saved"
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"
msgstr "Абонент не найден"
#: abonapp/views.py:645
#: abonapp/views.py:656
msgid "Passport info for the user does not exist"
msgstr "Для абонента не найдены паспортные данные"
#: abonapp/views.py:682
#: abonapp/views.py:693
msgid "Device has successfully attached"
msgstr "Устройство успешно прикреплено"
#: abonapp/views.py:687
#: abonapp/views.py:698
msgid "Device your selected already does not exist"
msgstr "Устройство, выбранное вами, уже не существует"
#: abonapp/views.py:707
#: abonapp/views.py:718
msgid "Device has successfully unattached"
msgstr "Устройство успешно откреплено"
#: abonapp/views.py:753
#: abonapp/views.py:764
msgid "Group what you want doesn't exist"
msgstr "Указанная вами группа не найдена"
#: abonapp/views.py:777
#: abonapp/views.py:789
msgid "Extra field successfully created"
msgstr "Динамичесое поле добавлено успешно"
#: abonapp/views.py:808
#: abonapp/views.py:819
msgid "Extra fields has been saved"
msgstr "Динамические поля сохранены"
#: abonapp/views.py:810
#: abonapp/views.py:821
msgid "One or more extra fields has not been saved"
msgstr "Поле или одно из полей не найдено"
#: abonapp/views.py:822
#: abonapp/views.py:833
msgid "Extra field successfully deleted"
msgstr "Динамическое поле успешно удалено"
#: abonapp/views.py:832
#: abonapp/views.py:843
msgid "no ping"
msgstr "не пингуется"
#: abonapp/views.py:839 abonapp/views.py:849
#: abonapp/views.py:850 abonapp/views.py:860
msgid "ping ok"
msgstr "пингуется"
#: abonapp/views.py:844
#: abonapp/views.py:855
#, python-format
msgid "ok ping, %d/%d loses"
msgstr "пингуется, %d/%d"
#: abonapp/views.py:847
#: abonapp/views.py:858
#, python-format
msgid "no ping, %d/%d loses"
msgstr "не пингуется, %d/%d"
#: abonapp/templates/abonapp/ext.html:31
msgid "Services"
msgstr "Услуги"
msgid "Payment history"
msgstr "История платежей"
#: abonapp/templates/abonapp/ext.html:43
msgid "Payments"
msgstr "Финансы"
#: abonapp/templates/abonapp/ext.html:48
msgid "History of tasks"
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"
msgstr "Графики"
#: abonapp/templates/abonapp/ext.html:26
msgid "Sub information"
msgstr "Информация абонента"
msgid "Streets"
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"
msgstr "Эта услуга уже подключена"
msgid "Service already activated"
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
from __future__ import unicode_literals
import abonapp.fields
from django.db import migrations, models
import django.db.models.deletion
import djing.fields
from django.db import migrations, models
class Migration(migrations.Migration):
@ -18,7 +18,7 @@ class Migration(migrations.Migration):
name='Opt82',
fields=[
('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)),
],
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 tariff_app.models import Tariff
from accounts_app.models import UserProfile
from .fields import MACAddressField
from mydefs import MyGenericIPAddressField, ip2int, LogicError, ip_addr_regex
from djing import settings
class AbonGroup(models.Model):
@ -41,16 +41,6 @@ class AbonLog(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')
# время начала действия услуги
@ -68,9 +58,9 @@ class AbonTariff(models.Model):
return False if self.time_start is None else True
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:
@ -133,18 +123,6 @@ class ExtraFieldsModel(models.Model):
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):
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)
@ -154,7 +132,9 @@ class Abon(UserProfile):
street = models.ForeignKey(AbonStreet, on_delete=models.SET_NULL, null=True, blank=True)
house = models.CharField(max_length=12, null=True, 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):
@ -188,6 +168,9 @@ class Abon(UserProfile):
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.tariff == tariff:
# Эта услуга уже подключена
@ -256,18 +239,6 @@ class Abon(UserProfile):
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):
series = models.CharField(max_length=4, 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):
timeout = None
if hasattr(instance, 'is_dhcp') and instance.is_dhcp:
timeout = 14400
timeout = getattr(settings, 'DHCP_TIMEOUT', 14400)
agent_abon = instance.build_agent_struct()
if agent_abon is None:
return True
@ -377,5 +348,14 @@ def abon_del_signal(sender, instance, **kwargs):
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)
if pays.count() > 0:
return bad_ret(-100)
# тут в author передаём учётку абонента, т.к. это он сам через терминал пополняет
abon.add_ballance(abon, pay_amount, comment='AllPay %.2f' % pay_amount)
abon.save(update_fields=['ballance'])
AllTimePayLog.objects.create(
pay_id=pay_id,
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")
return "<?xml version='1.0' encoding='UTF-8'?>" \
"<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 %}

72
abonapp/templates/abonapp/buy_tariff.html

@ -22,41 +22,55 @@
<form role="form" action="{% url 'abonapp:pick_tariff' abon_group.pk abon.pk %}"
method="post">{% csrf_token %}
<div class="form-group">
<label for="id_tariffs">{% trans 'Pick a service' %}</label>
{% if tariffs %}
<label for="id_tariffs">{% trans 'Pick a service' %}</label>
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-bullhorn"></span></span>
<select class="form-control" name="tariff" id="id_tariffs">
{% for trf in tariffs %}
{% 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)
</option>
{% endfor %}
</select>
</div>
{% if not abon.active_tariff %}
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-calendar"></span></span>
<input type="text" class="form-control" name="deadline" id="id_deadline" value="{{ tariffs.0.calc_deadline|date:"Y-m-d" }}">
<script type="text/javascript">
$(function () {
$('#id_deadline').datetimepicker({
format: 'YYYY-MM-DD'
});
$('#id_tariffs').on('change', function(){
var a = $(this).find('option:selected');
$('#id_deadline').val(a.attr('data-deadline'));
});
});
</script>
</div>
{% 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>
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-bullhorn"></span></span>
<select class="form-control" name="tariff" id="id_tariffs">
{% for trf in tariffs %}
<option value="{{ trf.pk }}" data-deadline="{{ trf.calc_deadline|date:"Y-m-d" }}">
{{ trf.title }}. {{ trf.amount }}{% trans 'currency' %} (Вх:{{ trf.speedIn }}MBit/s. Исх:{{ trf.speedOut }} MBit/s)
</option>
{% endfor %}
</select>
</div>
{% if not abon.active_tariff %}
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-calendar"></span></span>
<input type="text" class="form-control" name="deadline" id="id_deadline" value="{{ tariffs.0.calc_deadline|date:"Y-m-d" }}">
<script type="text/javascript">
$(function () {
$('#id_deadline').datetimepicker({
format: 'YYYY-MM-DD'
});
$('#id_tariffs').on('change', function(){
var a = $(this).find('option:selected');
$('#id_deadline').val(a.attr('data-deadline'));
});
});
</script>
</div>
{% endif %}
</div>
<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' %}
</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' %}
</button>
</div>

75
abonapp/templates/abonapp/editAbon.html

@ -31,7 +31,7 @@
<div class="input-group input-group-sm">
{{ form.telephone }}{{ form.telephone.errors }}
<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>
</a>
</span>
@ -42,7 +42,7 @@
<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>
<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>
@ -113,23 +113,6 @@
</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 %}
<div class="form-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="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{% trans 'DHCP information' %}</h3>
<h3 class="panel-title">{% trans 'Select the device' %}</h3>
</div>
<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">
<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>
{% if device %}
<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">
{{ 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 class="form-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">
<span class="glyphicon glyphicon-floppy-disk"></span> {% trans 'Save' %}
</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>
{% endif %}
</form>
</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>

6
abonapp/templates/abonapp/modal_dev.html

@ -10,12 +10,14 @@
<div class="modal-body">
<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">
<select name="dev" id="id_dev" class="form-control">
{% for device in devices %}
<option value="{{ device.pk }}">{{ device }}</option>
{% if device.has_attachable_to_subscriber %}
<option value="{{ device.pk }}">{{ device }}</option>
{% endif %}
{% empty %}
<option value="0">---</option>
{% 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">
<thead>
<tr>
<th>#</th>
<th>
<a href="{% url 'abonapp:people_list' abon_group.pk %}?order_by=username&dir={{ dir|default:"down" }}">
{% trans 'Sub' %}
@ -45,7 +46,7 @@
</th>
<th width="100">
<a href="{% url 'abonapp:people_list' abon_group.pk %}?order_by=house&dir={{ dir|default:"down" }}">
{% trans 'House' %}/{% trans 'Apartment' %}
{% trans 'Apartment' %}
</a>
{% if order_by == 'house' %}<span class="glyphicon glyphicon-filter"></span>{% endif %}
</th>
@ -57,31 +58,38 @@
</a>
{% if order_by == 'ballance' %}<span class="glyphicon glyphicon-filter"></span>{% endif %}
</th>
<th width="100">#</th>
<th width="10">#</th>
</tr>
</thead>
<tbody>
{% with can_ch_trf=perms.tariff_app.change_tariff can_del_trf=perms.abonapp.delete_abon %}
{% for human in peoples %}
{% if human.is_active %}
<tr>
{% else %}
<tr class="danger">
{% 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>
<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>
{% 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 %}
</td>
<td>{{ human.ip_address|default:_('Not assigned') }}</td>
<td>{{ human.fio }}</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>
{% if human.active_tariff %}
@ -95,7 +103,7 @@
</td>
<td>{{ human.ballance }}</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">
<span class="glyphicon glyphicon-remove"></span>
</a>
@ -112,6 +120,7 @@
</td>
</tr>
{% endfor %}
{% endwith %}
</tbody>
<tfoot>
<tr>
@ -142,6 +151,14 @@
{% empty %}
<a href="#" class="list-group-item">{% trans 'No streets found for that group' %}</a>
{% 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>

113
abonapp/templates/abonapp/service.html

@ -7,31 +7,58 @@
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{% trans 'Services of subscriber' %}</h3>
<h3 class="panel-title">{% trans "Subscriber's service" %}</h3>
</div>
<div class="panel-body">
{% if abon_tariff %}
<p>
<b>{% trans 'Service' %}</b>:
{% if perms.tariff_app.change_tariff %}
<a href="{% url 'tarifs:edit' abon_tariff.pk %}" title="{{ abon_tariff.time_start|default:'' }}">
{{ abon_tariff.tariff.title }}
</a>
{% else %}
{{ abon_tariff.tariff.title }}
{% 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>
<dl class="dl-horizontal">
<dt>{% trans 'Service' %}</dt>
<dd>
{% if abon_tariff.tariff %}
{% if perms.tariff_app.change_tariff %}
<a href="{% url 'tarifs:edit' abon_tariff.tariff.pk %}" title="{{ abon_tariff.time_start|default:'' }}">
{{ abon_tariff.tariff.title }}
</a>
{% else %}
{{ abon_tariff.tariff.title }}
{% endif %}
{% else %}
<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 %}
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' %}
</a>
{% trans 'Subscriber has no service' %}.
<a href="{% url 'abonapp:pick_tariff' abon_group.pk abon.pk %}">
{% trans 'Buy service' %}
</a>
{% 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>
@ -41,14 +68,52 @@
<h3 class="panel-title">Услуги для заказа</h3>
</div>
<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>
{% endblock %}

9
abonapp/urls_abon.py

@ -6,6 +6,9 @@ urlpatterns = [
url(r'^$', views.peoples, name='people_list'),
url(r'^addabon$', views.addabon, name='add_abon'),
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+)/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+)/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+)/opt82$', views.opt82, name='opt82'),
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+)/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/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'),
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+)/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')
]

204
abonapp/views.py

@ -4,6 +4,7 @@ from django.contrib.gis.shortcuts import render_to_text
from django.core.exceptions import PermissionDenied
from django.db import IntegrityError, ProgrammingError
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.contrib.auth.decorators import login_required, permission_required
from django.utils import timezone
@ -11,16 +12,17 @@ from django.http import HttpResponse
from django.contrib import messages
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 agent import NasFailedResult, Transmitter, NasNetworkError
from . import forms
from . import models
import mydefs
from devapp.models import Device
from devapp.models import Device, Port as DevPort
from datetime import datetime, date
from taskapp.models import Task
from dialing_app.models import AsteriskCDR
from statistics.models import getModel, get_dates
@login_required
@ -33,8 +35,6 @@ def peoples(request, gid):
else:
peoples_list = peoples_list.filter(group=gid)
StatModel = getModel()
# фильтр
dr, field = mydefs.order_helper(request)
if field:
@ -44,10 +44,10 @@ def peoples(request, gid):
peoples_list = mydefs.pag_mn(request, peoples_list)
for abon in peoples_list:
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:
messages.warning(request, e)
@ -191,6 +191,7 @@ def delentity(request):
@login_required
@permission_required('abonapp.can_add_ballance')
@atomic
def abonamount(request, gid, uid):
abon = get_object_or_404(models.Abon, pk=uid)
try:
@ -244,12 +245,14 @@ def pay_history(request, gid, uid):
@login_required
@mydefs.only_admins
def abon_services(request, gid, uid):
grp = get_object_or_404(models.AbonGroup, pk=gid)
abon = get_object_or_404(models.Abon, pk=uid)
return render(request, 'abonapp/service.html', {
'abon': abon,
'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)
frm = None
passw = None
abon_device = None
try:
if request.method == 'POST':
if not request.user.has_perm('abonapp.change_abon'):
raise PermissionDenied
frm = forms.AbonForm(request.POST, instance=abon)
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()
messages.success(request, _('edit abon success msg'))
else:
@ -281,7 +277,8 @@ def abonhome(request, gid, uid):
else:
passw = models.AbonRawPassword.objects.get(account=abon).passw_text
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:
messages.error(request, e)
passw = models.AbonRawPassword.objects.get(account=abon).passw_text
@ -291,8 +288,6 @@ def abonhome(request, gid, uid):
messages.error(request, e)
except models.AbonRawPassword.DoesNotExist:
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:
for err in errs.err_list:
messages.add_message(request, messages.constants.ERROR, err)
@ -304,8 +299,8 @@ def abonhome(request, gid, uid):
'abon_group': abon_group,
'ip': abon.ip_address,
'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:
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):
from .pay_systems import allpay
ret_text = allpay(request)
@ -395,6 +357,7 @@ def add_invoice(request, gid, uid):
@login_required
@permission_required('abonapp.can_buy_tariff')
@atomic
def pick_tariff(request, gid, uid):
grp = get_object_or_404(models.AbonGroup, pk=gid)
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', {
'tariffs': tariffs,
'abon': abon,
'abon_group': grp
'abon_group': grp,
'selected_tariff': mydefs.safe_int(request.GET.get('selected_tariff'))
})
@login_required
@permission_required('abonapp.can_complete_service')
@atomic
def complete_service(request, gid, uid, srvid):
abtar = get_object_or_404(models.AbonTariff, pk=srvid)
abon = abtar.abon
@ -489,12 +454,11 @@ def complete_service(request, gid, uid, srvid):
})
@login_required
@permission_required('abonapp.delete_abontariff')
def unsubscribe_service(request, gid, uid, srvid):
def unsubscribe_service(request, gid, uid, abon_tariff_id):
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'))
except NasFailedResult as e:
messages.error(request, e)
@ -503,7 +467,7 @@ def unsubscribe_service(request, gid, uid, srvid):
except mydefs.MultipleException as errs:
for err in errs.err_list:
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
@ -569,11 +533,15 @@ def passport_view(request, gid, uid):
try:
abon = models.Abon.objects.get(pk=uid)
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():
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'))
return redirect('abonapp:passport_view', gid=gid, uid=uid)
else:
@ -615,24 +583,20 @@ def chgroup_tariff(request, gid):
def dev(request, gid, uid):
abon_dev = None
try:
abon = models.Abon.objects.get(pk=uid)
if request.method == 'POST':
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)
messages.success(request, _('Device has successfully attached'))
abon.device = dev
abon.save(update_fields=['device'])
messages.success(request, _('Device has successfully attached'))
return redirect('abonapp:abon_home', gid=gid, uid=uid)
else:
abon_dev = models.AbonDevice.objects.get(abon=uid).device
abon_dev = abon.device
except Device.DoesNotExist:
messages.warning(request, _('Device your selected already does not exist'))
except models.Abon.DoesNotExist:
messages.error(request, _('Abon does not exist'))
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', {
'devices': Device.objects.filter(user_group=gid),
'dev': abon_dev,
@ -645,8 +609,8 @@ def dev(request, gid, uid):
def clear_dev(request, gid, uid):
try:
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'))
except models.Abon.DoesNotExist:
messages.error(request, _('Abon does not exist'))
@ -657,7 +621,6 @@ def clear_dev(request, gid, uid):
@login_required
@mydefs.only_admins
def charts(request, gid, uid):
from statistics.models import getModel
high = 100
wandate = request.GET.get('wantdate')
@ -705,7 +668,7 @@ def charts(request, gid, uid):
'abon': abon,
'charts_data': ',\n'.join(charts_data) if charts_data is not None else None,
'high': high,
'dates': StatElem.objects.get_dates()
'dates': get_dates()
})
@ -809,7 +772,6 @@ def abon_ping(request):
def dials(request, gid, uid):
abon = get_object_or_404(models.Abon, pk=uid)
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)
if abon.telephone is not None and abon.telephone != '':
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
def abons(request):
@ -853,5 +897,5 @@ def abons(request):
def search_abon(request):
word = request.GET.get('s')
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))

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

@ -23,10 +23,6 @@ msgstr ""
msgid "Users must have an telephone number"
msgstr "У пользователей должен быть номер телефона"
#: accounts_app/models.py:52
msgid "Telephone number"
msgstr "Номер телефона"
#: accounts_app/templates/accounts/group.html:7
#: accounts_app/templates/accounts/group_list.html:7
msgid "Administrators"
@ -81,7 +77,7 @@ msgstr "Добавить группу"
#: accounts_app/templates/accounts/index.html:8
#: accounts_app/templates/accounts/settings/ch_info.html:37
msgid "Telephone"
msgstr "Номер телефона"
msgstr "Телефон"
#: accounts_app/templates/accounts/index.html:12
#: accounts_app/templates/accounts/login.html:35
@ -140,27 +136,35 @@ msgstr "Старый пароль"
msgid "New password"
msgstr "Новый пароль"
#: accounts_app/views.py:43
#: accounts_app/views.py:42
msgid "Wrong login or password, please try again"
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"
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"
msgstr "Забыли указать пароль для нового аккаунта"
#: accounts_app/views.py:165
#: accounts_app/views.py:171
msgid "You forget to repeat a password for the new account"
msgstr "Забыли повторить пароль для нового аккаунта"
#: accounts_app/views.py:174
#: accounts_app/views.py:180
msgid "Subscriber with this name already exist"
msgstr "Пользователь с таким именем уже есть"
#: accounts_app/views.py:176
#: accounts_app/views.py:182
msgid "Passwords does not match, try again"
msgstr "Пароли не совпадают, попробуйте ещё раз"
@ -184,3 +188,9 @@ msgstr "Редактировать"
msgid "Set a task"
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)
telephone = models.CharField(
max_length=16,
verbose_name=_('Telephone number'),
verbose_name=_('Telephone'),
#unique=True,
validators=[RegexValidator('^\+[7,8,9,3]\d{10,11}$')]
)

2
accounts_app/templates/accounts/index.html

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

39
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.urlresolvers import NoReverseMatch
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 import messages
from django.utils.translation import ugettext as _
@ -103,15 +102,20 @@ def chgroup(request, uid):
@mydefs.only_admins
def ch_ava(request):
if request.method == 'POST':
user = request.user
if user.avatar:
user.avatar.delete()
photo = Photo()
photo.image = request.FILES.get('avatar')
photo.save()
user.avatar = photo
user.save(update_fields=['avatar'])
request.user = user
phname = request.FILES.get('avatar')
if phname is None:
messages.error(request, _('Please select an image'))
else:
user = request.user
if user.avatar:
user.avatar.delete()
photo = Photo()
photo.image = phname
photo.save()
user.avatar = photo
user.save(update_fields=['avatar'])
request.user = user
messages.success(request, _('Avatar successfully changed'))
return render(request, 'accounts/settings/ch_info.html', {
'user': request.user
@ -129,14 +133,21 @@ def ch_info(request):
user.telephone = request.POST.get('telephone')
psw = request.POST.get('oldpasswd')
if psw != '':
if psw != '' and psw is not None:
if user.check_password(psw):
newpasswd = request.POST.get('newpasswd')
user.set_password(newpasswd)
if newpasswd != '' and newpasswd is not None:
user.set_password(newpasswd)
user.save()
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'))
user.save()
request.user = user
else:
messages.warning(request, _('Empty password, fill it'))
return render(request, 'accounts/settings/ch_info.html', {
'user': request.user

44
agent/commands/dhcp.py

@ -1,33 +1,39 @@
# -*- coding: utf-8 -*-
from django.core.exceptions import MultipleObjectsReturned
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):
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:
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.is_dhcp = True
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:
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):

31
agent/core.py

@ -99,3 +99,34 @@ class BaseTransmitter(metaclass=ABCMeta):
:param count: количество пингов
: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 )

140
agent/mod_mikrotik.py

@ -5,13 +5,13 @@ from abc import ABCMeta
from hashlib import md5
from .core import BaseTransmitter, NasFailedResult, NasNetworkError
from mydefs import ping
from .structs import TariffStruct, AbonStruct, IpStruct, ShapeItem
from .structs import TariffStruct, AbonStruct, IpStruct
from . import settings
from djing.settings import DEBUG
import re
#DEBUG=False
#DEBUG=True
LIST_USERS_ALLOWED = 'DjingUsersAllowed'
LIST_USERS_BLOCKED = 'DjingUsersBlocked'
@ -33,7 +33,7 @@ class ApiRos:
md.update(bytes(pwd, 'utf-8'))
md.update(chal)
for r in self.talk_iter(["/login", "=name=" + username,
"=response=00" + binascii.hexlify(md.digest()).decode('utf-8')]): pass
"=response=00" + binascii.hexlify(md.digest()).decode('utf-8')]): pass
def talk_iter(self, words):
if self.writeSentence(words) == 0: return
@ -190,28 +190,29 @@ class TransmitterManager(BaseTransmitter, metaclass=ABCMeta):
res = text_speed_digit
elif text_append == 'k':
res = text_speed_digit / 1000
#elif text_append == 'G':
# elif text_append == 'G':
# res = text_speed_digit * 0x400
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
speeds = info['=max-limit'].split('/')
t = TariffStruct(
speedIn=parse_speed(speeds[1]),
speedOut=parse_speed(speeds[0])
)
try:
speeds = info['=max-limit'].split('/')
t = TariffStruct(
speedIn=parse_speed(speeds[1]),
speedOut=parse_speed(speeds[0])
)
a = AbonStruct(
uid=int(info['=name'][3:]),
#FIXME: тут в разных микротиках или =target-addresses или =target
# FIXME: тут в разных микротиках или =target-addresses или =target
ip=info['=target'][:-3],
tariff=t,
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):
@ -223,50 +224,55 @@ class QueueManager(TransmitterManager, metaclass=ABCMeta):
def add(self, user):
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',
'=name=uid%d' % user.uid,
#FIXME: тут в разных микротиках или =target-addresses или =target
'=target=%s' % user.ip.get_str(),
'=max-limit=%.3fM/%.3fM' % (user.tariff.speedOut, user.tariff.speedIn),
'=queue=MikroBILL_SFQ/MikroBILL_SFQ',
'=burst-time=1/1'
])
'=name=uid%d' % user.uid,
# FIXME: тут в разных микротиках или =target-addresses или =target
'=target=%s' % str(user.ip),
'=max-limit=%.3fM/%.3fM' % (user.tariff.speedOut, user.tariff.speedIn),
'=queue=MikroBILL_SFQ/MikroBILL_SFQ',
'=burst-time=1/1'
])
def remove(self, user):
assert isinstance(user, AbonStruct)
q = self.find('uid%d' % user.uid)
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):
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):
assert isinstance(user, AbonStruct)
if user.tariff is None or not isinstance(user.tariff, TariffStruct):
return
queue = self.find('uid%d' % user.uid)
if queue is None:
# не нашли запись в шейпере об абоненте, добавим
return self.add(user)
else:
mk_id = queue.sid
mk_id = getattr(queue, 'queue_id', '')
# обновляем шейпер абонента
return self._exec_cmd(['/queue/simple/set', '=.id=*' + mk_id,
'=name=uid%d' % user.uid,
'=max-limit=%.3fM/%.3fM' % (user.tariff.speedOut, user.tariff.speedIn),
#FIXME: тут в разных микротиках или =target-addresses или =target
'=target=%s' % user.ip.get_str(),
'=queue=MikroBILL_SFQ/MikroBILL_SFQ',
'=burst-time=1/1'
])
return self._exec_cmd(['/queue/simple/set', '=.id=' + mk_id,
'=name=uid%d' % user.uid,
'=max-limit=%.3fM/%.3fM' % (user.tariff.speedOut, user.tariff.speedIn),
# FIXME: тут в разных микротиках или =target-addresses или =target
'=target=%s' % str(user.ip),
'=queue=MikroBILL_SFQ/MikroBILL_SFQ',
'=burst-time=1/1'
])
# читаем шейпер, возващаем записи о шейпере
def read_queue_iter(self):
queues = self._exec_cmd_iter(['/queue/simple/print', '=detail'])
for queue in queues:
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):
@ -282,7 +288,7 @@ class QueueManager(TransmitterManager, metaclass=ABCMeta):
self.add(user)
return self.disable(user)
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):
assert isinstance(user, AbonStruct)
@ -291,7 +297,13 @@ class QueueManager(TransmitterManager, metaclass=ABCMeta):
self.add(user)
self.enable(user)
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):
@ -301,7 +313,7 @@ class IpAddressListManager(TransmitterManager, metaclass=ABCMeta):
commands = [
'/ip/firewall/address-list/add',
'=list=%s' % list_name,
'=address=%s' % ip.get_str()
'=address=%s' % str(ip)
]
if type(timeout) is int:
commands.append('=timeout=%d' % timeout)
@ -311,22 +323,36 @@ class IpAddressListManager(TransmitterManager, metaclass=ABCMeta):
if timeout is not None:
commands = [
'/ip/firewall/address-list/set', '=.id=' + str(mk_id),
'=timeout=%d' % timeout
'=timeout=%d' % timeout
]
return self._exec_cmd(commands)
def remove(self, mk_id):
return self._exec_cmd([
'/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):
assert isinstance(ip, IpStruct)
return self._exec_cmd([
'/ip/firewall/address-list/print', 'where',
'?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):
@ -355,18 +381,18 @@ class MikrotikTransmitter(QueueManager, IpAddressListManager):
self.add_user(usr)
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)]
for ip in ips:
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'])
def add_user(self, user, ip_timeout=None):
assert isinstance(user.tariff, TariffStruct)
assert isinstance(user.ip, IpStruct)
if user.tariff is None or not isinstance(user.tariff, TariffStruct):
return
QueueManager.add(self, user)
IpAddressListManager.add(self, LIST_USERS_ALLOWED, user.ip, ip_timeout)
# удаляем из списка заблокированных абонентов
@ -377,12 +403,11 @@ class MikrotikTransmitter(QueueManager, IpAddressListManager):
def remove_user(self, user):
QueueManager.remove(self, user)
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'])
# обновляем основную инфу абонента
def update_user(self, user, ip_timeout=None):
assert isinstance(user.tariff, TariffStruct)
assert isinstance(user.ip, IpStruct)
# ищем ip абонента в списке ip
@ -396,6 +421,13 @@ class MikrotikTransmitter(QueueManager, IpAddressListManager):
IpAddressListManager.remove(self, find_res[0]['=.id'])
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 возвращает пустой словарь в списке если ничего нет)
if len(find_res) < 2:
# добавим запись об абоненте
@ -410,7 +442,7 @@ class MikrotikTransmitter(QueueManager, IpAddressListManager):
if queue is None:
QueueManager.add(self, user)
return
if queue.abon != user:
if queue != user:
QueueManager.update(self, user)
def ping(self, host, count=10):
@ -423,14 +455,14 @@ class MikrotikTransmitter(QueueManager, IpAddressListManager):
interface = r[0]['=interface']
r = self._exec_cmd([
'/ping', '=address=%s' % host, '=arp-ping=yes', '=interval=100ms', '=count=%d' % count,
'=interface=%s' % interface
'=interface=%s' % interface
])
received, sent = int(r[-2:][0]['=received']), int(r[-2:][0]['=sent'])
return received, sent
# приостановливаем обслуживание абонента
def pause_user(self, user):
pass
self.remove_user(user)
# продолжаем обслуживание абонента
def start_user(self, user):
@ -454,3 +486,9 @@ class MikrotikTransmitter(QueueManager, IpAddressListManager):
def remove_tariff(self, tid):
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])
return self
def get_str(self):
return int2ip(self.__ip)
def get_int(self):
return self.__ip
@ -44,22 +41,32 @@ class IpStruct(BaseStruct):
assert isinstance(other, IpStruct)
return self.__ip == other.__ip
def __int__(self):
return self.__ip
def __str__(self):
return int2ip(self.__ip)
def __hash__(self):
return hash(self.__ip)
# Как обслуживается абонент
class TariffStruct(BaseStruct):
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):
dt = pack("!Iff", int(self.tid), float(self.speedIn), float(self.speedOut))
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):
dt = unpack("!Iff", data)
self.tid = int(dt[0])
@ -68,7 +75,6 @@ class TariffStruct(BaseStruct):
return self
def __eq__(self, other):
assert isinstance(other, TariffStruct)
# не сравниваем id, т.к. тарифы с одинаковыми скоростями для NAS одинаковы
# Да и иногда не удобно доставать из nas id тарифы из базы
return self.speedIn == other.speedIn and self.speedOut == other.speedOut
@ -76,6 +82,11 @@ class TariffStruct(BaseStruct):
def __str__(self):
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):
@ -83,14 +94,15 @@ class AbonStruct(BaseStruct):
def __init__(self, uid=None, ip=None, tariff=None, is_active=True):
self.uid = int(uid)
self.ip = IpStruct(ip)
assert isinstance(tariff, TariffStruct)
self.tariff = tariff
self.is_active = is_active
def serialize(self):
if self.tariff is None:
return
assert isinstance(self.tariff, TariffStruct)
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
def deserialize(self, data, tariff=None):
@ -110,7 +122,10 @@ class AbonStruct(BaseStruct):
return r
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

1
bugs.txt

@ -11,3 +11,4 @@
!!! Обязательно проверить как отрабатывает на 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"
"%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."
msgstr "Давай знакомиться, как тебя зовут? Напиши свой логин из биллинга."
#: chatbot/telebot.py:80
#: chatbot/telebot.py:81
msgid "I do not know the answer to this yet."
msgstr "Я пока не знаю ответа на это"
#: chatbot/telebot.py:99
#: chatbot/telebot.py:100
msgid ""
"You are not found in the database, check that it correctly pointed out its "
"LOGIN. Try again"
msgstr ""
"Ты не найден в базе, проверь что правильно указал именно свой ЛОГИН. Попробуй ещё"
"Ты не найден в базе, проверь что правильно указал именно свой ЛОГИН. "
"Попробуй ещё"
#: chatbot/telebot.py:122
#: chatbot/telebot.py:123
msgid "It's not like ip address, try again"
msgstr "Это не похоже на ip адрес, попробуй ещё"
#: chatbot/telebot.py:125
#: chatbot/telebot.py:126
#, python-format
msgid "You're '%s', right?"
msgstr "Ты ведь %s ?"
#: chatbot/telebot.py:135
#: chatbot/telebot.py:136
#, python-format
msgid "Recipient '%s' does not subscribed on notifications"
msgstr "%s не подписан на оповещения"

2
chatbot/telebot.py

@ -113,7 +113,7 @@ class DjingTelebot(helper.ChatHandler):
# пингуем адрес
def ping(self, ip=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
try:
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?"
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"
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"
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
msgid "conducted payments"
msgstr "Проведённые платежи"
@ -162,3 +96,44 @@ msgstr "Комментарий"
#: clientsideapp/templates/clientsideapp/pays.html:25
msgid "You have not spent payments"
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/clientside/my_clientside.js"></script>
<link rel="shortcut icon" href="/static/img/favicon_m.ico">
/head>
</head>
<body>
<!-- Modal -->
<div class="modal fade" id="modFrm" tabindex="-1" role="dialog" aria-hidden="true">
@ -71,13 +71,11 @@
</li>
</ul>
<span class="navbar-text">Ваш балланс <b>{{ subscriber.ballance }}</b> руб.</span>
</div>
<!--/.nav-collapse -->
</div><!--/.nav-collapse -->
</div>
</div>
<!-- Begin page content -->
<div class="container">
{% if request.user.is_staff %}

3
clientsideapp/templates/clientsideapp/index.html

@ -9,8 +9,7 @@
<li><b>Телефоны:</b> +79788328885, +79788318999</li>
<li><b>Адрес:</b> пос. Нижнегорский, ул. Победы 38. 2й этаж старого дома быта, возле рынка</li>
</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>

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 class="modal-body">
<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>
@ -25,7 +19,7 @@ Cost: {{ amount }} rubles.{% endblocktrans %}</p>-->
</div>
<div class="modal-footer">
<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 type="button" class="btn btn-default" data-dismiss="modal">{% trans 'Close' %}</button>
</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>

134
clientsideapp/templates/clientsideapp/services.html

@ -1,81 +1,72 @@
{% extends 'clientsideapp/ext.html' %}
{% load i18n %}
{% block client_main %}
<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="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>
<span class="badge">{{ abon_tariff.tariff.amount }}</span>
{% if abon_tariff.is_started %}
<b>{{ abon_tariff.tariff.title }}</b>
{% else %}
{{ abon_tariff.tariff.title }}
{% endif %}
</li>
{% empty %}
<li class="list-group-item">У вас нет заказанных услуг</li>
{% endfor %}
</ul>
</div>
<div class="col-lg-8">
<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>
<div class="container-fluid">
<div class="row">
{% for tarif in tarifs %}
<div class="col-lg-12">
<h3>{{ tarif.title }}</h3>
<dt>Время завершения услуги</dt>
<dd>{{ current_service.deadline|date:"d E Y, l" }}</dd>
<i>{{ tarif.amount }} руб.</i>
<p>{{ tarif.descr }}</p>
<dt>Стоимость</dt>
<dd>{{ current_service.tariff.amount }} {% trans 'currency' %}</dd>
</dl>
<p>{{ current_service.tariff.descr }}</p>
</div>
</div>
{% else %}
<div class="alert alert-warning" role="alert">
<span class="glyphicon glyphicon-exclamation-sign"></span>
<strong>Внимание!</strong> У вас нет услуги, для использования ресурсов приобретите нужную услугу из представленных тут.
</div>
{% endif %}
</div>
<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="row">
{% for tarif in tarifs %}
<div class="col-lg-12">
<h3>{{ tarif.title }}</h3>
{% if request.user.is_staff %}
<a href="#" class="btn btn-primary disabled">
{% else %}
<a href="{% url 'client_side:buy_service' tarif.id %}" class="btn btn-primary btn-modal">
{% endif %}
<span class="glyphicon glyphicon-shopping-cart"></span> Заказать
</a>
</div>
{% empty %}
<div class="col-lg-4">
<h3 class="panel-title">Нет доступных услуг для заказа</h3>
<i>{{ tarif.amount }} руб.</i>
<p>{{ tarif.descr }}</p>
{% if request.user.is_staff or current_service %}
<a href="#" class="btn btn-primary disabled">
{% else %}
<a href="{% url 'client_side:buy_service' tarif.id %}" class="btn btn-primary btn-modal">
{% endif %}
<span class="glyphicon glyphicon-shopping-cart"></span> {% trans 'Pick' %}
</a>
</div>
{% empty %}
<div class="col-lg-4">
<h3 class="panel-title">Нет доступных услуг для заказа</h3>
</div>
{% endfor %}
</div>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
</div>
@ -89,14 +80,11 @@
Как работает личный кабинет, раздел услуг
</div>
<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>

3
clientsideapp/urls.py

@ -8,9 +8,6 @@ urlpatterns = [
url(r'^pays$', views.pays, name='pays'),
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+)/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/(?P<d_id>\d+)$', views.debt_buy, name='debt_buy')
]

116
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.shortcuts import render, get_object_or_404, redirect
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 tariff_app.models import Tariff
from mydefs import pag_mn, RuTimedelta, LogicError
from mydefs import pag_mn, LogicError
from agent import NasFailedResult, NasNetworkError
@ -29,28 +30,24 @@ def pays(request):
def services(request):
abon = Abon.objects.get(pk=request.user.pk)
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', {
'tarifs': all_tarifs,
'own_abon_tariffs': own_abon_tariffs,
'current_service': current_service
'current_service': abon.active_tariff()
})
@login_required
@atomic
def buy_service(request, srv_id):
abon = get_object_or_404(Abon, pk=request.user.pk)
service = get_object_or_404(Tariff, pk=srv_id)
try:
current_service = abon.active_tariff()
if request.method == 'POST':
abon.pick_tariff(service, request.user, 'Покупка тарифного плана через личный кабинет, тариф "%s"'
% service)
messages.success(request, 'Вы подписались на новую услугу. Она встала на очередь подключений. '
'Когда закончится ваша текущая услуга, то включится эта')
abon.pick_tariff(service, request.user, _("Buy the service via user side, service '%s'")
% service)
messages.success(request, _("The service '%s' wan successfully activated") % service.title)
else:
return render_to_text('clientsideapp/modal_service_buy.html', {
'service': service,
@ -63,98 +60,6 @@ def buy_service(request, srv_id):
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
def debts_list(request):
debts = InvoiceForPayment.objects.filter(abon=request.user)
@ -164,6 +69,7 @@ def debts_list(request):
@login_required
@atomic
def debt_buy(request, d_id):
debt = get_object_or_404(InvoiceForPayment, id=d_id)
abon = get_object_or_404(Abon, id=request.user.id)
@ -171,9 +77,9 @@ def debt_buy(request, d_id):
try:
sure = request.POST.get('sure')
if sure != 'on':
raise LogicError('Вы не уверены что хотите оплатить долг?')
raise LogicError(_("Are you not sure that you want buy the service?"))
if abon.ballance < debt.amount:
raise LogicError('Не достаточно средств на счету')
raise LogicError(_('Your account have not enough money'))
abon.make_pay(request.user, debt.amount)
debt.set_ok()

1
devapp/admin.py

@ -5,4 +5,3 @@ from . import models
admin.site.register(models.Device)
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):
"""Получаем путь к html шаблону отображения устройства"""
@staticmethod
@abstractmethod
def has_attachable_to_subscriber():
"""Можно-ли подключать устройство к абоненту"""
@staticmethod
@abstractmethod
def is_use_device_port():
"""True если при авторизации по opt82 используется порт"""
class BasePort(object, metaclass=ABCMeta):
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
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):
def __init__(self, num, name, status, mac, speed, snmpWorker):
@ -31,15 +15,13 @@ class DLinkPort(BasePort):
# выключаем этот порт
def disable(self):
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):
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')
def reboot(self):
pass
return self.get_item('.1.3.6.1.4.1.2021.8.1.101.1')
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 = []
ln = len(macs)
ln = len(speeds)
for n in range(ln):
status = True if int(stats[n]) == 1 else False
res.append(DLinkPort(
@ -75,21 +57,29 @@ class DLinkDevice(DevBase, SNMPBaseWorker):
return res
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):
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())
return tm
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):
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.signal = signal
@ -113,7 +103,7 @@ class OLTDevice(DevBase, SNMPBaseWorker):
@staticmethod
def description():
return _('PON ONU')
return _('PON OLT')
def reboot(self):
pass
@ -146,10 +136,104 @@ class OLTDevice(DevBase, SNMPBaseWorker):
return tm
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 -*-
from django import forms
from django.utils.translation import ugettext as _
from django.db import IntegrityError
from . import models
from mydefs import ip_addr_regex
from djing import MAC_ADDR_REGEX
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:
model = models.Device
fields = '__all__'
@ -13,10 +25,9 @@ class DeviceForm(forms.ModelForm):
'ip_address': forms.TextInput(attrs={
'pattern': ip_addr_regex,
'placeholder': '192.168.0.100',
'required': True,
'class': 'form-control'
}),
'comment': forms.Textarea(attrs={
'comment': forms.TextInput(attrs={
'required': True,
'class': 'form-control'
}),
@ -31,5 +42,32 @@ class DeviceForm(forms.ModelForm):
}),
'user_group': forms.Select(attrs={
'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
# This file is distributed under the same license as the PACKAGE package.
# Dmitry Novikov nerosketch@gmail.com, 2017.
@ -32,8 +31,8 @@ msgid "does not fetch the mac"
msgstr "не нашёл мак"
#: 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/devices.html:7
@ -41,14 +40,14 @@ msgstr "ONU Голова"
#: devapp/templates/devapp/group_list.html:7
#: devapp/templates/devapp/group_list.html:10
msgid "Groups"
msgstr ""
msgstr "Группы"
#: devapp/templates/devapp/add_dev.html:8
msgid "Add new device"
msgstr ""
msgstr "Добавить устройство"
#: devapp/templates/devapp/add_dev.html:14
#: devapp/templates/devapp/ports.html:143
#: devapp/templates/devapp/ports.html:93
msgid "Not assigned"
msgstr "&lt;Не назначено&gt;"
@ -84,7 +83,7 @@ msgstr "Точка топологии"
#: devapp/templates/devapp/add_dev.html:71 devapp/templates/devapp/dev.html:59
msgid "User group"
msgstr ""
msgstr "Группа"
#: devapp/templates/devapp/add_dev.html:81 devapp/templates/devapp/dev.html:69
msgid "Save"
@ -118,7 +117,7 @@ msgstr "Устройства без группы"
#: devapp/templates/devapp/group_list.html:18
msgid "Group title"
msgstr ""
msgstr "Название"
#: devapp/templates/devapp/group_list.html:28
#, fuzzy
@ -128,12 +127,15 @@ msgstr "Эта точка не пингуется"
#: devapp/templates/devapp/group_list.html:37
msgid "Add group"
msgstr ""
msgstr "Добавить группу"
#: devapp/templates/devapp/olt.html:13
msgid "Mac"
msgstr "Мак"
msgid "Mac address"
msgstr "Мак адрес"
#: devapp/templates/devapp/olt.html:14
msgid "Name"
msgstr "Имя"
@ -148,7 +150,7 @@ msgstr "Ур. сигнала"
#: devapp/templates/devapp/olt.html:34
msgid "Ports not found"
msgstr "Онушки не получил"
msgstr "Порты не найдены"
#: devapp/templates/devapp/ports.html:9
msgid "Title of the type of switch"
@ -158,39 +160,50 @@ msgstr "Название типа свича"
msgid "Uptime"
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 :("
msgstr "Инфа не получена, проверьте настройки :("
#: devapp/templates/devapp/ports.html:54
#: devapp/templates/devapp/ports.html:55
msgid "Device log"
msgstr "Лог устройства"
#: devapp/templates/devapp/ports.html:61
#: devapp/templates/devapp/ports.html:62
msgid "Level"
msgstr "Уровень"
#: devapp/templates/devapp/ports.html:62
#: devapp/templates/devapp/ports.html:63
msgid "Description"
msgstr "Описание"
#: devapp/templates/devapp/ports.html:63
#: devapp/templates/devapp/ports.html:64
msgid "Date"
msgstr "Дата"
#: devapp/templates/devapp/ports.html:129
#: devapp/templates/devapp/ports.html:79
msgid "Ports comment"
msgstr "Комментарии портов"
#: devapp/templates/devapp/ports.html:135
#: devapp/templates/devapp/ports.html:85
msgid "Port"
msgstr "Порт"
#: devapp/templates/devapp/ports.html:136
msgid "Ports"
msgstr "Порты"
#: devapp/templates/devapp/ports.html:86
msgid "Title"
msgstr "Название"
#: devapp/templates/devapp/ports.html:147
#: devapp/templates/devapp/ports.html:97
msgid "We have not received info for ports"
msgstr "Инфа о портах не получена"
@ -224,3 +237,66 @@ msgstr "Ошибка SNMP на устройстве"
msgid "Edit"
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(
model_name='device',
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 -*-
from django.db import models
from djing.fields import MACAddressField
from .base_intr import DevBase
from mydefs import MyGenericIPAddressField, MyChoicesAdapter
from .dev_types import DEVICE_TYPES
from . import dev_types
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):
ip_address = MyGenericIPAddressField()
mac_addr = MACAddressField(null=True, blank=True, unique=True)
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)
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)
parent_dev = models.ForeignKey('self', blank=True, null=True, on_delete=models.SET_NULL)
class Meta:
db_table = 'dev'
@ -37,25 +49,57 @@ class Device(models.Model):
return res
return
# Можно-ли подключать устройство к абоненту
def has_attachable_to_subscriber(self):
mngr_class = self.get_manager_klass()
return mngr_class.has_attachable_to_subscriber()
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):
PORT_SPEEDS = (
('h', '100Mbps'),
('k', '1Gbps'),
('d', '10Gbps')
)
device = models.ForeignKey(Device)
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:
db_table = 'dev_port'
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">
<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' group.pk %}">{{ group.title }}</a></li>
<li class="active">{% trans 'Add new device' %}</li>
</ol>
{% include 'message_block.html' %}
<div class="page-header">
<h2>{{ dev.comment|default:_('Not assigned') }}</h2>
<h2>{{ group.title|default:_('Not assigned') }}</h2>
</div>
<div class="panel panel-default">
@ -20,7 +21,7 @@
</div>
<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">
<label for="id_ip_address">{% trans 'Ip address' %}</label>
@ -31,6 +32,23 @@
</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">
<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 %}
{% block content %}
<div class="row">
<div class="col-sm-12">
<div class="table-responsive">
{% if uptime %}
{% trans 'Uptime' %} {{ uptime }}
{% endif %}
<table class="table table-striped table-bordered">
<thead>
<tr>
<th width="50">#</th>
<th width="10">#</th>
<th>{% trans 'Mac' %}</th>
<th>{% trans 'Name' %}</th>
<th>{% trans 'Distance(m)' %}</th>
<th width="250">{% trans 'Signal' %}</th>
<th width="10">#</th>
</tr>
</thead>
<tbody>
{% with dip=dev.ip_address grp=dev.user_group.pk %}
{% for port in ports %}
<tr>
<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 %}
</td>
<td>{{ port.mac }}</td>
<td>{{ port.nm }}</td>
<td>{{ port.sp }}</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>
{% empty %}
<tr>
<td colspan="5">{% trans 'Ports not found' %}</td>
<td colspan="6">{% trans 'Ports not found' %}</td>
</tr>
{% endfor %}
{% endwith %}
</tbody>
</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-heading">
<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 class="panel-body">
{% for port in ports %}
{% if port.st %}
{% if port.sp == 10 %}
<div class="port kilo">
<div class="port kilo text-center">
<b>10 mbps</b>
{% elif port.sp == 100 %}
<div class="port mega">
<div class="port mega text-center">
<b>100 mbps</b>
{% 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 %}
<div class="port">
<div class="port text-center">
{% endif %}
{% else %}
<div class="port dis">
<div class="port dis text-center">
{% endif %}
<a href="#" class="port-img" title="{{ port.nm }}">
<a href="javascript:void(0);" class="port-img" title="{{ port.nm }}">
<b>{{ port.num }}</b>
</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>
</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>
{% empty %}
<h3>{% trans 'We have not received info, please check options :(' %}</h3>
@ -65,58 +75,7 @@
</thead>
<tbody>
<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>
</tbody>
</table>

36
devapp/templates/devapp/dev.html

@ -8,7 +8,7 @@
</div>
<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">
<label for="id_ip_address">{% trans 'Ip address' %}</label>
@ -19,6 +19,23 @@
</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">
<label for="id_comment">{% trans 'Comment' %}</label>
@ -64,6 +81,23 @@
</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">
<button type="submit" class="btn btn-sm btn-primary">
<span class="glyphicon glyphicon-save"></span> {% trans 'Save' %}

20
devapp/templates/devapp/devices.html

@ -27,6 +27,7 @@
</a>
{% if order_by == 'comment' %}<span class="glyphicon glyphicon-filter"></span>{% endif %}
</th>
<th>{% trans 'Mac address' %}</th>
<th width="250">
<a href="{% url 'devapp:devs' group.pk %}?order_by=devtype&dir={{ dir|default:"down" }}">
{% trans 'Device type' %}
@ -38,19 +39,21 @@
</thead>
<tbody>
{% with can_del_dev=perms.devapp.delete_device can_ch_dev=perms.devapp.change_device %}
{% for dev in devices %}
<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.mac_addr }}</td>
<td>{{ dev.get_devtype_display }}</td>
<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>
</a>
{% 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>
</a>
{% endif %}
@ -58,15 +61,16 @@
</tr>
{% empty %}
<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>
{% endfor %}
{% endwith %}
</tbody>
<tfoot>
<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' %}
</a>
</td>

4
devapp/templates/devapp/devices_null_group.html

@ -58,7 +58,7 @@
</tr>
{% empty %}
<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>
{% endfor %}
</tbody>
@ -66,7 +66,7 @@
<tfoot>
<tr>
<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' %}
</a>
</td>

9
devapp/templates/devapp/ext.htm

@ -21,7 +21,7 @@
<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 %}>
<a href="{{ devapp_view }}">
{% trans 'Ports' %} {{ dev.ip_address }}
@ -29,10 +29,15 @@
</li>
{% 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 %}>
<a href="{{ devapp_edit }}">{% trans 'Edit' %}</a>
</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 %}
</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 = [
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'^(?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+)/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)
]

258
devapp/views.py

@ -1,15 +1,20 @@
# -*- coding: utf-8 -*-
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.db.models import Q
from django.http import HttpResponse
from django.shortcuts import render, redirect, get_object_or_404, resolve_url
from django.contrib import messages
from django.utils.translation import ugettext_lazy as _
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 .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
@ -61,12 +66,16 @@ def devdel(request, did):
return res_success(request, back_url)
except Device.DoesNotExist:
return res_error(request, _('Delete failed'))
except DeviceDBException as e:
return res_error(request, e)
@login_required
@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
user_group = get_object_or_404(AbonGroup, pk=grp)
already_dev = None
if request.method == 'POST':
if devid == 0:
@ -77,32 +86,220 @@ def dev(request, devid=0):
raise PermissionDenied
frm = DeviceForm(request.POST, instance=devinst)
if frm.is_valid():
frm.save()
ndev = frm.save()
messages.success(request, _('Device info has been saved'))
return redirect('devapp:edit', grp, ndev.pk)
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'))
else:
frm = DeviceForm(instance=devinst)
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:
frm = DeviceForm(instance=devinst)
if devinst is None:
return render(request, 'devapp/add_dev.html', {
'form': frm
'form': frm,
'group': user_group,
'already_dev': already_dev
})
else:
return render(request, 'devapp/dev.html', {
'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
@only_admins
def devview(request, did):
ports = None
uptime = 0
dev = get_object_or_404(Device, id=did)
template_name = 'devapp/ports.html'
template_name = 'ports.html'
try:
if ping(dev.ip_address):
if dev.man_passw:
@ -118,11 +315,14 @@ def devview(request, did):
messages.error(request, _('wait for a reply from the SNMP Timeout'))
except EasySNMPError:
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,
'ports': ports,
'uptime': uptime
'uptime': uptime,
'dev_accs': Abon.objects.filter(device=dev)
})
@ -132,18 +332,21 @@ def toggle_port(request, did, portid, status=0):
portid = int(portid)
status = int(status)
dev = get_object_or_404(Device, id=int(did))
if ping(dev.ip_address):
if dev.man_passw:
manager = dev.get_manager_klass()(dev.ip_address, dev.man_passw)
ports = manager.get_ports()
if status:
ports[portid-1].enable()
try:
if ping(dev.ip_address):
if dev.man_passw:
manager = dev.get_manager_klass()(dev.ip_address, dev.man_passw)
ports = manager.get_ports()
if status:
ports[portid-1].enable()
else:
ports[portid-1].disable()
else:
ports[portid-1].disable()
messages.warning(request, _('Not Set snmp device password'))
else:
messages.warning(request, _('Not Set snmp device password'))
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)
@ -154,3 +357,14 @@ def group_list(request):
return render(request, 'devapp/group_list.html', {
'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"
"%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"
msgstr "Не отвечен"
#: dialing_app/models.py:9
#: dialing_app/models.py:9 dialing_app/models.py:45
msgid "Failed"
msgstr "С ошибкой"
#: dialing_app/models.py:10
#: dialing_app/models.py:10 dialing_app/models.py:47
msgid "Busy"
msgstr "Занято"
#: dialing_app/models.py:11
#: dialing_app/models.py:11 dialing_app/models.py:49
msgid "Answered"
msgstr "Отвечен"
#: dialing_app/models.py:12
#: dialing_app/models.py:12 dialing_app/models.py:51
msgid "Unknown"
msgstr "Не определён"
#: dialing_app/templates/index.html:8
#: dialing_app/templates/index.html:9
msgid "Dialing"
msgstr "Звонки"
#: dialing_app/templates/index.html:11
#: dialing_app/templates/index.html:12
msgid "Last calls"
msgstr "Последние звонки"
#: dialing_app/templates/index.html:19
#: dialing_app/templates/index.html:20
msgid "Play"
msgstr "Слушать"
#: dialing_app/templates/index.html:21
msgid "calldate"
msgstr "дата звонка"
#: dialing_app/templates/index.html:20
#: dialing_app/templates/index.html:22
msgid "src"
msgstr "кто"
#: dialing_app/templates/index.html:21
#: dialing_app/templates/index.html:23
msgid "dst"
msgstr "куда"
#: dialing_app/templates/index.html:26
#: dialing_app/templates/index.html:24
msgid "duration"
msgstr "продолжительность"
#: dialing_app/templates/index.html:28
#: dialing_app/templates/index.html:25
msgid "start"
msgstr "начало"
#: dialing_app/templates/index.html:29
#: dialing_app/templates/index.html:26
msgid "answer"
msgstr "ответ"
#: dialing_app/templates/index.html:30
#: dialing_app/templates/index.html:27
msgid "end"
msgstr "конец"
#: dialing_app/templates/index.html:31
#: dialing_app/templates/index.html:28
msgid "disposition"
msgstr "состояние"
#: dialing_app/templates/index.html:57
#: dialing_app/templates/index.html:50
msgid "Calls was not found"
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"
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 ''
@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:
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 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">
<table class="table table-striped table-bordered">
@ -31,10 +21,14 @@
<tbody>
{% for log in logs %}
<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>{{ log.calldate|date:'d E Y, H:i:s' }}</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
@stringfilter
def abon_if_telephone(value):
print(value, type(value))
"""Возвращаем ссыль на абонента если передали номер телефона"""
if re.match(r'^\+?\d+$', value):
if value[0] != '+':
value = '+'+value
url = resolve_url('dialapp:to_abon', tel=value)
a = '<a href="%s" target="_blank">%s</a>' % (url, value)
print(a)
return a
else:
return value

3
dialing_app/urls.py

@ -4,5 +4,6 @@ from . import views
urlpatterns = [
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
@only_admins
def home(request):
logs = AsteriskCDR.objects.all()
logs = AsteriskCDR.objects.exclude(userfield='request').order_by('-calldate')
logs = pag_mn(request, logs)
title = _('Last calls')
return render(request, 'index.html', {
'logs': logs
'logs': logs,
'title': title
})
@ -34,3 +36,14 @@ def to_abon(request, tel):
else:
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.utils.translation import ugettext_lazy as _
from netaddr import EUI, AddrFormatError
from . import MAC_ADDR_REGEX
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.'),
code='invalid',
)

4
djing/settings_example.py

@ -162,3 +162,7 @@ pay_SERV_ID = '<service id>'
pay_SECRET = '<secret>'
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'^client/', include('clientsideapp.urls', namespace='client_side')),
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)
]

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>
<div class="list-group">
{% 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 }}
</a>
{% empty %}

7
photo_app/models.py

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

8
queue_mngr.py

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

1
requirements.txt

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

2
searchapp/views.py

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

11
static/css/custom.css

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

72
static/js/my.js

@ -27,7 +27,7 @@ $(document).ajaxError(function (ev, jqXHR, ajaxSettings, thrownError) {
$.fn.selectajax = function (opt) {
var settings = $.extend( {
url : '/api'
url : this.attr('data-dst')
}, opt);
var selectbtn = this.children('button.selectajax-btn');
@ -40,7 +40,6 @@ $(document).ajaxError(function (ev, jqXHR, ajaxSettings, thrownError) {
var hr = a.attr('href');
var tx = a.text();
selecthid.val(hr.substr(1));
console.debug(tx);
selectbtn.text(tx).removeClass('hidden');
selectinp.addClass('hidden').val(tx);
};
@ -49,7 +48,7 @@ $(document).ajaxError(function (ev, jqXHR, ajaxSettings, thrownError) {
$.getJSON(settings.url, {'s': this.value}, function (r) {
selectul.empty();
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);
li.on('click', selectajax_click)
});
@ -70,16 +69,55 @@ $(document).ajaxError(function (ev, jqXHR, ajaxSettings, thrownError) {
})(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
if (window.File && window.FileReader && window.FileList && window.Blob) {
@ -114,9 +152,7 @@ $(document).ready(function () {
});
$('div.selectajax').selectajax({
url: '/abons/api/abon_filter'
});
$('div.selectajax').selectajax();
$('[data-toggle=offcanvas]').click(function () {
$('.row-offcanvas').toggleClass('active');
@ -147,6 +183,10 @@ $(document).ready(function () {
self.html(r.dat);
});
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
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 .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 byte_to_mbit(x):
@ -27,6 +21,8 @@ class StatManager(models.Manager):
def split_list(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)]
def avarage(elements):
@ -42,7 +38,7 @@ class StatManager(models.Manager):
charts_times = split_list(charts_times, count_of_parts)
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]
midnight = datetime.combine(want_date, time.min)
charts_data.append("{x:new Date(%d),y:0}" % (int(charts_times[-1:][0]) + 1))
@ -51,11 +47,6 @@ class StatManager(models.Manager):
else:
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):
cur_time = UnixDateTimeField(primary_key=True)
@ -65,11 +56,19 @@ class StatElem(models.Model):
objects = StatManager()
# ReadOnly
def save(self, *args, **kwargs):
return
pass
# ReadOnly
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
def percentile(N, percent, key=lambda x:x):
@ -97,10 +96,27 @@ class StatElem(models.Model):
abstract = True
def getModel(want_date=datetime.now()):
def getModel(want_date=now()):
class DynamicStatElem(StatElem):
class Meta:
db_table = 'flowstat_%s' % want_date.strftime("%d%m%Y")
abstract = False
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