Browse Source

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

# Conflicts:
#	README.md
devel
bashmak 9 years ago
parent
commit
5ab1feb89f
  1. 3
      Doc.txt
  2. 23
      README.md
  3. 59
      abonapp/__init__.py
  4. 2
      abonapp/admin.py
  5. 25
      abonapp/forms.py
  6. 599
      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. 272
      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. 24
      abonapp/templates/abonapp/charts.html
  15. 47
      abonapp/templates/abonapp/dial_log.html
  16. 82
      abonapp/templates/abonapp/editAbon.html
  17. 5
      abonapp/templates/abonapp/ext.htm
  18. 38
      abonapp/templates/abonapp/modal_addstreet.html
  19. 6
      abonapp/templates/abonapp/modal_dev.html
  20. 38
      abonapp/templates/abonapp/modal_editstreet.html
  21. 78
      abonapp/templates/abonapp/peoples.html
  22. 119
      abonapp/templates/abonapp/service.html
  23. 101
      abonapp/templates/abonapp/services.html
  24. 225
      abonapp/tests.py
  25. 12
      abonapp/urls_abon.py
  26. 330
      abonapp/views.py
  27. 32
      accounts_app/locale/ru/LC_MESSAGES/django.po
  28. 21
      accounts_app/migrations/0007_auto_20170816_1109.py
  29. 2
      accounts_app/models.py
  30. 4
      accounts_app/templates/accounts/index.html
  31. 39
      accounts_app/views.py
  32. 50
      agent/commands/dhcp.py
  33. 31
      agent/core.py
  34. 140
      agent/mod_mikrotik.py
  35. 6
      agent/netflow/djing_flow.conf
  36. 35
      agent/structs.py
  37. 4
      bugs.txt
  38. 15
      chatbot/locale/ru/LC_MESSAGES/django.po
  39. 2
      chatbot/telebot.py
  40. 111
      clientsideapp/locale/ru/LC_MESSAGES/django.po
  41. 6
      clientsideapp/templates/clientsideapp/ext.html
  42. 3
      clientsideapp/templates/clientsideapp/index.html
  43. 28
      clientsideapp/templates/clientsideapp/modal_activate_service.html
  44. 32
      clientsideapp/templates/clientsideapp/modal_complete_service.html
  45. 10
      clientsideapp/templates/clientsideapp/modal_service_buy.html
  46. 26
      clientsideapp/templates/clientsideapp/modal_unsubscribe_service.html
  47. 134
      clientsideapp/templates/clientsideapp/services.html
  48. 3
      clientsideapp/urls.py
  49. 118
      clientsideapp/views.py
  50. 2
      cron.py
  51. 1
      devapp/admin.py
  52. 10
      devapp/base_intr.py
  53. 158
      devapp/dev_types.py
  54. 42
      devapp/forms.py
  55. 164
      devapp/locale/ru/LC_MESSAGES/django.po
  56. 2
      devapp/migrations/0005_auto_20170502_2232.py
  57. 48
      devapp/migrations/0006_auto_20170705_1403.py
  58. 20
      devapp/migrations/0007_auto_20170816_1109.py
  59. 74
      devapp/models.py
  60. 34
      devapp/onu_register.sh
  61. 24
      devapp/templates/devapp/add_dev.html
  62. 18
      devapp/templates/devapp/custom_dev_page/olt.html
  63. 42
      devapp/templates/devapp/custom_dev_page/onu.html
  64. 89
      devapp/templates/devapp/custom_dev_page/ports.html
  65. 38
      devapp/templates/devapp/dev.html
  66. 20
      devapp/templates/devapp/devices.html
  67. 4
      devapp/templates/devapp/devices_null_group.html
  68. 11
      devapp/templates/devapp/ext.htm
  69. 93
      devapp/templates/devapp/manage_ports/add_ports.html
  70. 60
      devapp/templates/devapp/manage_ports/list.html
  71. 37
      devapp/templates/devapp/manage_ports/modal_add_edit_port.html
  72. 18
      devapp/templates/devapp/manage_ports/modal_del_port.html
  73. 12
      devapp/urls.py
  74. 258
      devapp/views.py
  75. 73
      dhcp_lever.py
  76. 0
      dialing_app/__init__.py
  77. 3
      dialing_app/admin.py
  78. 5
      dialing_app/apps.py
  79. 108
      dialing_app/locale/ru/LC_MESSAGES/django.po
  80. 43
      dialing_app/migrations/0001_initial.py
  81. 0
      dialing_app/migrations/__init__.py
  82. 63
      dialing_app/models.py
  83. 40
      dialing_app/templates/ext.html
  84. 53
      dialing_app/templates/index.html
  85. 57
      dialing_app/templates/vmail.html
  86. 0
      dialing_app/templatetags/__init__.py
  87. 20
      dialing_app/templatetags/telephone_filters.py
  88. 3
      dialing_app/tests.py
  89. 9
      dialing_app/urls.py
  90. 49
      dialing_app/views.py
  91. 38
      djing/__init__.py
  92. 0
      djing/fields.py
  93. 3
      djing/formfields.py
  94. 11
      djing/settings_example.py
  95. 1
      djing/urls.py
  96. 32
      djing/utils/load_dot_from_nodeny.py
  97. 92
      djing/utils/load_from_nodeny.py
  98. 45
      djing/utils/push_snmp_passw.py
  99. 29
      djing/utils/save_dot_from_nodeny.py
  100. 280
      djing/utils/save_from_nodeny.py

3
Doc.txt

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

23
README.md

@ -2,25 +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
```
На Fedora ставил так
```
# dnf install uwsgi python3 python3-devel nginx python-pip git redis mariadb mariadb-devel net-snmp net-snmp-devel gcc redhat-rpm-config uwsgi-plugin-python3
# pip3 install rq mysqlclient easysnmp xmltodict netaddr telepot Pillow Django==1.9 uwsgi
```
## Содержание
* [Установка](./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'})
}

599
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-03-25 00:40+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"
@ -19,27 +19,35 @@ 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"
#: abonapp/forms.py:29 abonapp/templates/abonapp/addAbon.html.py:21
#: abonapp/formfields.py:12
msgid "Enter a valid integer."
msgstr "Введите верное число"
#: abonapp/formfields.py:21
msgid "Enter a valid MAC Address."
msgstr "Введите валидный mac адрес"
#: 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:42 abonapp/templates/abonapp/editAbon.html.py:22
#: abonapp/templates/abonapp/peoples.html:33
#: 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:47
#: abonapp/forms.py:60
msgid "telephone placeholder"
msgstr "+[7,8,9,3] и 10,11 цифр"
#: abonapp/models.py:26
#: abonapp/models.py:23
msgid "fill account"
msgstr "Пополнение счёта"
#: abonapp/models.py:119
#: abonapp/models.py:116
msgid "not enough money"
msgstr "Не хватает денег на счету"
@ -47,10 +55,6 @@ msgstr "Не хватает денег на счету"
msgid "finish service perm"
msgstr "Снятие со счёта средств"
#: abonapp/models.py:145
msgid "activate service perm"
msgstr "Активация услуги абонента"
#: abonapp/models.py:162
msgid "Digital field"
msgstr "Цифровое поле"
@ -63,31 +67,35 @@ msgstr "Текстовое поле"
msgid "Floating field"
msgstr "Дробное с плавающей точкой"
#: abonapp/models.py:178
#: abonapp/models.py:165 abonapp/templates/abonapp/editAbon.html.py:43
#: abonapp/templates/abonapp/viewAbon.html:50
msgid "Ip Address"
msgstr "IP Адрес"
#: abonapp/models.py:190
msgid "Double invalid value"
msgstr "Введите число с плавающей запятой"
#: abonapp/models.py:219
#: abonapp/models.py:244
msgid "Buy service perm"
msgstr "Покупка тарифа абоненту"
#: abonapp/models.py:220
#: abonapp/models.py:245
msgid "Can view passport"
msgstr "Может просматривать паспортные данные"
#: abonapp/models.py:224
msgid "pay log"
msgstr "Снятие со счёта средств"
#: abonapp/models.py:270
#: abonapp/models.py:292
msgid "Buy service default log"
msgstr "Покупка тарифного плана через админку"
#: abonapp/models.py:285
#: abonapp/models.py:307
msgid "service overdue log"
msgstr "Услуга просрочена, отключаем, и подключаем новую"
#: abonapp/templates/abonapp/activate_service.html:8
#: abonapp/models.py:347
msgid "Ip address already exist"
msgstr "Такой ip уже у кого-то есть"
#: abonapp/templates/abonapp/addAbon.html:7
#: abonapp/templates/abonapp/addGroup.html:7
#: abonapp/templates/abonapp/addInvoice.html:7
@ -101,49 +109,32 @@ msgstr "Услуга просрочена, отключаем, и подключ
#: abonapp/templates/abonapp/invoiceForPayment.html:7
#: abonapp/templates/abonapp/log.html:7
#: abonapp/templates/abonapp/peoples.html:8
#: abonapp/templates/abonapp/peoples.html:114
#: 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:86
#: abonapp/templates/abonapp/addAbon.html:91
#: abonapp/templates/abonapp/addGroup.html:29
#: abonapp/templates/abonapp/addInvoice.html:46
#: abonapp/templates/abonapp/buy_tariff.html:38
#: 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:103
#: abonapp/templates/abonapp/editAbon.html:192
#: abonapp/templates/abonapp/editAbon.html:238
#: abonapp/templates/abonapp/group_tariffs.html:29
#: abonapp/templates/abonapp/passport_view.html:42
#: abonapp/templates/abonapp/modal_dev.html:29
#: abonapp/templates/abonapp/passport_view.html:49
msgid "Save"
msgstr "Сохранить"
#: abonapp/templates/abonapp/addAbon.html:9
#: abonapp/templates/abonapp/addAbon.html:16
#: abonapp/templates/abonapp/peoples.html:97
#: abonapp/templates/abonapp/peoples.html:108
#: abonapp/templates/abonapp/peoples.html:115
#: abonapp/templates/abonapp/peoples.html:126
msgid "Add abon"
msgstr "Добавить абонента"
@ -153,13 +144,13 @@ msgstr "Фамилия и Имя"
#: abonapp/templates/abonapp/addAbon.html:37
#: abonapp/templates/abonapp/editAbon.html:29
#: abonapp/templates/abonapp/peoples.html:49
#: 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 "Группа"
@ -167,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
@ -176,29 +167,31 @@ msgid "Comment"
msgstr "Комментарий"
#: abonapp/templates/abonapp/addAbon.html:59
#: abonapp/templates/abonapp/editAbon.html:43
#: abonapp/templates/abonapp/peoples.html:39
#: 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:45
#: 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 "Пароль"
#: abonapp/templates/abonapp/addAbon.html:89
#: abonapp/templates/abonapp/addAbon.html:94
#: abonapp/templates/abonapp/addGroup.html:32
#: abonapp/templates/abonapp/addInvoice.html:49
#: abonapp/templates/abonapp/buy_tariff.html:41
#: abonapp/templates/abonapp/buy_tariff.html:60
#: abonapp/templates/abonapp/group_tariffs.html:29
#: abonapp/templates/abonapp/modal_abonamount.html:26
#: abonapp/templates/abonapp/modal_abonamount.html:25
#: abonapp/templates/abonapp/modal_dev.html:32
#: abonapp/templates/abonapp/modal_extra_field.html:40
msgid "Reset"
msgstr "Сбросить"
@ -245,10 +238,28 @@ msgstr "Купить новую услугу (заказать тариф) дл
#: abonapp/templates/abonapp/debtors.html:20
#: abonapp/templates/abonapp/log.html:19
#: abonapp/templates/abonapp/payHistory.html:8
#: abonapp/templates/abonapp/peoples.html:21
#: abonapp/templates/abonapp/peoples.html:24
msgid "Sub"
msgstr "Абонент"
#: abonapp/templates/abonapp/buy_tariff.html:32
#: abonapp/templates/abonapp/group_tariffs.html:24
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
@ -310,36 +321,133 @@ 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:36
#: abonapp/templates/abonapp/editAbon.html:117
#: abonapp/templates/abonapp/editAbon.html:135
#: abonapp/templates/abonapp/viewAbon.html:50
msgid "Ip Address"
msgstr "IP Адрес"
#: 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
#: abonapp/templates/abonapp/viewAbon.html:37
#: abonapp/templates/abonapp/viewAbon.html:42
#: abonapp/templates/abonapp/viewAbon.html:47
#: abonapp/templates/abonapp/viewAbon.html:51
msgid "Not assigned"
msgstr "&lt;Не назначен&gt;"
#: abonapp/templates/abonapp/editAbon.html:51
#: abonapp/templates/abonapp/peoples.html:45
#: 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:111
#: 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:123
msgid "Remove clutch"
msgstr "Удалить муфту"
#: abonapp/templates/abonapp/editAbon.html:127
#: abonapp/templates/abonapp/modal_dev.html:6
msgid "Add clutch"
msgstr "Добавить муфту"
#: abonapp/templates/abonapp/editAbon.html:170
msgid "DHCP information"
msgstr "DHCP информация"
#: abonapp/templates/abonapp/editAbon.html:176
msgid "Mac Address"
msgstr "Мак"
#: abonapp/templates/abonapp/editAbon.html:183
msgid "Port"
msgstr "Порт"
#: abonapp/templates/abonapp/editAbon.html:194
msgid "Reset option82"
msgstr "Сбросить option82"
#: abonapp/templates/abonapp/editAbon.html:195
#: abonapp/templates/abonapp/editAbon.html:219
msgid "Delete"
msgstr "Удалить"
#: abonapp/templates/abonapp/editAbon.html:178
msgid "Extra fields"
msgstr "Динамические записи"
#: abonapp/templates/abonapp/editAbon.html:201 abonapp/views.py:824
msgid "Extra field does not exist"
msgstr "Поле не найдено"
#: abonapp/templates/abonapp/editAbon.html:206
#: abonapp/templates/abonapp/modal_extra_field.html:6
msgid "Add extra field"
msgstr "Добавить новое динамическое поле"
#: abonapp/templates/abonapp/editAbon.html:207
#: abonapp/templates/abonapp/modal_extra_field.html:37
msgid "Add"
msgstr "Добавить"
#: abonapp/templates/abonapp/group_list.html:27
msgid "Number of subscribers"
msgstr "Количество абонентов"
@ -420,10 +528,26 @@ msgstr "Начисление средств на счёт"
msgid "Amount of money"
msgstr "Количество денег"
#: abonapp/templates/abonapp/modal_abonamount.html:23
#: abonapp/templates/abonapp/modal_abonamount.html:22
msgid "Refill"
msgstr "Пополнить"
#: abonapp/templates/abonapp/modal_dev.html:13
msgid "Select the device"
msgstr "Выберите устройство"
#: abonapp/templates/abonapp/modal_extra_field.html:11
msgid "Field title"
msgstr "Название поля"
#: abonapp/templates/abonapp/modal_extra_field.html:20
msgid "Field type"
msgstr "Тип динамического поля"
#: abonapp/templates/abonapp/modal_extra_field.html:28
msgid "Field content"
msgstr "Содержимое динамического поля"
#: abonapp/templates/abonapp/passport_view.html:9
#: abonapp/templates/abonapp/viewAbon.html:67
msgid "Passport information"
@ -444,8 +568,8 @@ msgid "Distributor"
msgstr "Кем выдан"
#: abonapp/templates/abonapp/passport_view.html:33
msgid "Birthday"
msgstr "Дата рождения"
msgid "Date of acceptance"
msgstr "Дата выдачи"
#: abonapp/templates/abonapp/payHistory.html:26
msgid "Payment history is empty"
@ -459,49 +583,72 @@ 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 "Траф."
#: abonapp/templates/abonapp/peoples.html:31
#, fuzzy
#| msgid "Ip Address"
msgid "Ip address"
msgstr "IP Адрес"
#: abonapp/templates/abonapp/peoples.html:50
#: abonapp/templates/abonapp/peoples.html:54
#: abonapp/templates/abonapp/services.html:10
msgid "Service"
msgstr "Услуга"
#: abonapp/templates/abonapp/peoples.html:53
#: abonapp/templates/abonapp/peoples.html:57
msgid "Ballance"
msgstr "Балланс"
#: abonapp/templates/abonapp/peoples.html:68
#: abonapp/templates/abonapp/peoples.html:70
#: abonapp/templates/abonapp/peoples.html:71
#: abonapp/templates/abonapp/viewAbon.html:18
#: abonapp/templates/abonapp/viewAbon.html:29
#: abonapp/templates/abonapp/viewAbon.html:33
#: abonapp/templates/abonapp/viewAbon.html:37
#: abonapp/templates/abonapp/viewAbon.html:42
#: abonapp/templates/abonapp/viewAbon.html:47
#: abonapp/templates/abonapp/viewAbon.html:51
msgid "Not assigned"
msgstr "&lt;Не назначен&gt;"
#: abonapp/templates/abonapp/peoples.html:95
#: abonapp/templates/abonapp/peoples.html:113
msgid "Subscribers not found"
msgstr "Абоненты не найдены"
#: abonapp/templates/abonapp/peoples.html:112
#: abonapp/templates/abonapp/peoples.html:130
msgid "Refresh subscribers on NAS"
msgstr "Обновить абонентов в NAS"
#: abonapp/templates/abonapp/peoples.html:115
#: abonapp/templates/abonapp/peoples.html:133
msgid "Tariffs in groups"
msgstr "Тарифы в группах"
#: 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"
@ -515,13 +662,9 @@ msgstr "Входящая скорость"
msgid "Output speed"
msgstr "Исходящая скорость"
#: abonapp/templates/abonapp/services.html:52
msgid "Priority up"
msgstr "Повысить приоритет"
#: abonapp/templates/abonapp/services.html:58
msgid "Priority down"
msgstr "Понизить приоритет"
#: abonapp/templates/abonapp/services.html:14
msgid "Works until"
msgstr "Действует до"
#: abonapp/templates/abonapp/services.html:64
msgid "Delete service"
@ -567,231 +710,241 @@ msgstr "Просмотр абонента"
msgid "yes,no"
msgstr "Да,Нет"
#: abonapp/views.py:51
#: abonapp/views.py:75
msgid "create group success msg"
msgstr "Группа успешно создана"
#: abonapp/views.py:54 abonapp/views.py:112 abonapp/views.py:253
#: 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:89 abonapp/views.py:154
#: abonapp/views.py:114 abonapp/views.py:177
msgid "delete group success msg"
msgstr "Группа успешно удалена"
#: abonapp/views.py:109
#: abonapp/views.py:135
msgid "create abon success msg"
msgstr "Абонент успешно создан"
#: abonapp/views.py:126
#: abonapp/views.py:149
msgid "Address"
msgstr "Адрес"
#: abonapp/views.py:148
#: abonapp/views.py:171
msgid "delete abon success msg"
msgstr "Абонент успешно удалён"
#: abonapp/views.py:157
#: abonapp/views.py:180
msgid "I not know what to delete"
msgstr "Не понятно что удалять"
#: abonapp/views.py:161
#: abonapp/views.py:184
#, python-format
msgid "NAS says: '%s'"
msgstr "NAS сказал: '%s'"
#: abonapp/views.py:174
#: abonapp/views.py:201
msgid "fill account through admin side"
msgstr "Пополнение счёта через админку"
#: abonapp/views.py:176
#: abonapp/views.py:203
#, python-format
msgid "Account filled successfully on %.2f"
msgstr ""
#: abonapp/views.py:179
#: abonapp/views.py:206
msgid "I not know the account id"
msgstr "Счёт успешно пополнен на %.2f"
#: abonapp/views.py:251
#: abonapp/views.py:282
msgid "edit abon success msg"
msgstr "Абонент успешно изменён"
#: abonapp/views.py:258
#, python-format
msgid "Ip address already exist"
msgstr "Такой ip уже у кого-то есть"
#: abonapp/views.py:268
#: abonapp/views.py:297
msgid "User has not have password, and cannot login"
msgstr "Для абонента не задан пароль, он не сможет войти в учётку"
#: abonapp/views.py:315
#: abonapp/views.py:299 abonapp/views.py:703
msgid "User device was not found"
msgstr "Пользовательское устройство не найдено"
#: abonapp/views.py:355
#, fuzzy
#| msgid "Abon does not exist"
msgid "User does not exist"
msgstr "Абонент не найден"
#: abonapp/views.py:388
msgid "Receipt has been created"
msgstr "Квитанция на оплату была создана"
#: abonapp/views.py:339
#: abonapp/views.py:419
msgid "Tariff has been picked"
msgstr "Тариф успешно выбран"
#: abonapp/views.py:349
#: abonapp/views.py:427
msgid "Tariff your picked does not exist"
msgstr "Тариф, который вы выбрали, не существует"
#: abonapp/views.py:406
#: abonapp/views.py:491
msgid "Refunds for unused resources"
msgstr "Возврат средств за неиспользованные ресурсы"
#: abonapp/views.py:412
#: abonapp/views.py:497
msgid "Service has been finished successfully"
msgstr "Услуга успешно завершена"
#: abonapp/views.py:415 abonapp/views.py:446
#: abonapp/views.py:500 abonapp/views.py:533
msgid "Not confirmed"
msgstr "Действие не подтверждено"
#: abonapp/views.py:449
#: abonapp/views.py:536
msgid "Service has been activated successfully"
msgstr "Услуга успешно активирована"
#: abonapp/views.py:474
#: abonapp/views.py:562
msgid "User has been detached from service"
msgstr "Абонент отвязан от услуги"
msgid "Sub information"
msgstr "Информация абонента"
msgid "Services"
msgstr "Услуги"
#: abonapp/views.py:645
msgid "Passport information has been saved"
msgstr "Информация о паспорте сохранена"
msgid "Payment history"
msgstr "История платежей"
#: abonapp/views.py:653 abonapp/views.py:700 abonapp/views.py:720
#: abonapp/views.py:761
msgid "Abon does not exist"
msgstr "Абонент не найден"
msgid "Payments"
msgstr "Финансы"
#: abonapp/views.py:656
msgid "Passport info for the user does not exist"
msgstr "Для абонента не найдены паспортные данные"
msgid "History of tasks"
msgstr "История задач"
#: abonapp/views.py:693
msgid "Device has successfully attached"
msgstr "Устройство успешно прикреплено"
msgid "Dynamic Field"
msgstr "Динамическое поле"
#: abonapp/views.py:698
msgid "Device your selected already does not exist"
msgstr "Устройство, выбранное вами, уже не существует"
msgid "Mac Address"
msgstr "Мак"
#: abonapp/views.py:718
msgid "Device has successfully unattached"
msgstr "Устройство успешно откреплено"
msgid "Port"
msgstr "Порт"
#: abonapp/views.py:764
msgid "Group what you want doesn't exist"
msgstr "Указанная вами группа не найдена"
msgid "Enter a valid MAC Address."
msgstr "Введите валидный mac адрес"
#: abonapp/views.py:789
msgid "Extra field successfully created"
msgstr "Динамичесое поле добавлено успешно"
msgid "Reset option82"
msgstr "Сбросить option82"
#: abonapp/views.py:819
msgid "Extra fields has been saved"
msgstr "Динамические поля сохранены"
msgid "Delete"
msgstr "Удалить"
#: abonapp/views.py:821
msgid "One or more extra fields has not been saved"
msgstr "Поле или одно из полей не найдено"
msgid "Instance of a option82 unexpectiadly disappeared"
msgstr "Экземпляр option82 неожиданно исчез из базы"
#: abonapp/views.py:833
msgid "Extra field successfully deleted"
msgstr "Динамическое поле успешно удалено"
msgid "SNMP error on device"
msgstr "Ошибка в SNMP на устройстве"
#: abonapp/views.py:843
msgid "no ping"
msgstr "не пингуется"
msgid "No streets found for that group"
msgstr "Не найдены улицы для группы"
#: abonapp/views.py:850 abonapp/views.py:860
msgid "ping ok"
msgstr "пингуется"
msgid "Date of acceptance"
msgstr "Дата выдачи"
#: abonapp/views.py:855
#, python-format
msgid "ok ping, %d/%d loses"
msgstr "пингуется, %d/%d"
msgid "Passport information has been saved"
msgstr "Информация о паспорте сохранена"
#: abonapp/views.py:858
#, python-format
msgid "no ping, %d/%d loses"
msgstr "не пингуется, %d/%d"
msgid "Abon does not exist"
msgstr "Абонент не найден"
#: abonapp/templates/abonapp/ext.html:31
msgid "Services"
msgstr "Услуги"
msgid "Passport info for the user does not exist"
msgstr "Для абонента не найдены паспортные данные"
#: abonapp/templates/abonapp/ext.html:43
msgid "Payments"
msgstr "Финансы"
msgid "currency"
msgstr "руб"
#: abonapp/templates/abonapp/ext.html:48
msgid "History of tasks"
msgstr "История задач"
#: abonapp/templates/abonapp/ext.html:53
msgid "Charts"
msgstr "Графики"
msgid "Group what you want doesn't exist"
msgstr "Указанная вами группа не найдена"
#: abonapp/templates/abonapp/ext.html:26
msgid "Sub information"
msgstr "Информация абонента"
msgid "User device was not found"
msgstr "Пользовательское устройство не найдено"
msgid "Streets"
msgstr "Улицы"
msgid "Add clutch"
msgstr "Добавить муфту"
msgid "Dialing"
msgstr "Звонки"
msgid "Remove clutch"
msgstr "Удалить муфту"
msgid "Device port"
msgstr "Порт&nbsp;устройства"
msgid "Select a device"
msgstr "Выберите устройство"
msgid "Device your selected already does not exist"
msgstr "Устройство, выбранное вами, уже не существует"
msgid "Ports does not exist"
msgstr "Порты не найдены"
msgid "Device has successfully attached"
msgstr "Устройство успешно прикреплено"
msgid "User port has been saved"
msgstr "Порт абонента успешно выбран"
msgid "Device has successfully unattached"
msgstr "Устройство успешно откреплено"
msgid "Selected port does not exist"
msgstr "Выбранный порт не существует"
msgid "Works until"
msgstr "Действует до"
msgid "Device"
msgstr "Устройство"
msgid "Do"
msgstr "Действия"
msgid "Is dynamic network settings"
msgstr "Динамические настройки по dhcp"
msgid "Last traffic"
msgstr "Последний траффик"
msgid "Method is not POST"
msgstr "Метод не POST"
msgid "Extra field successfully created"
msgstr "Динамичесое поле добавлено успешно"
msgid "Call to"
msgstr "Позвонить"
msgid "Extra field successfully deleted"
msgstr "Динамическое поле успешно удалено"
msgid "Extra field does not exist"
msgstr "Поле не найдено"
msgid "Extra fields"
msgstr "Динамические записи"
msgid "Add extra field"
msgstr "Добавить новое динамическое поле"
msgid "Add"
msgstr "Добавить"
msgid "Field title"
msgstr "Название поля"
msgid "That service already activated"
msgstr "Эта услуга уже подключена"
msgid "Field type"
msgstr "Тип динамического поля"
msgid "Service already activated"
msgstr "Услуга уже подключена"
msgid "Field content"
msgstr "Содержимое динамического поля"
msgid "We have a problem in DB: AbonTariff instance has no related to service"
msgstr "У нас проблема с БД: экземпляр AbonTariff не имеет отношения к тарифу"
msgid "Extra fields has been saved"
msgstr "Динамические поля сохранены"
msgid "Date of start"
msgstr "Дата начала"
msgid "One or more extra fields has not been saved"
msgstr "Поле или одно из полей не найдено"
msgid "Subscriber has no service"
msgstr "У абонента нет услуги"
msgid "ok ping, %d/%d loses"
msgstr "пингуется, %d/%d"
msgid "This group has no services"
msgstr "У этой группы нет услуг"
msgid "no ping, %d/%d loses"
msgstr "не пингуется, %d/%d"
msgid "Attach serices to groups"
msgstr "Привязать услуги к группам"
msgid "no ping"
msgstr "не пингуется"
msgid "Attach services to group"
msgstr "Привязать услуги к этой группе"
msgid "ping ok"
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',
),
]

272
abonapp/models.py

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
from datetime import datetime
from django.core.exceptions import ValidationError
from django.utils import timezone
from django.db import models
@ -8,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):
@ -42,107 +41,32 @@ class AbonLog(models.Model):
class AbonTariff(models.Model):
abon = models.ForeignKey('Abon')
tariff = models.ForeignKey(Tariff, related_name='linkto_tariff')
tariff_priority = models.PositiveSmallIntegerField(default=0)
# время начала действия, остальные что не начали действие - NULL
# время начала действия услуги
time_start = models.DateTimeField(null=True, blank=True, default=None)
# время завершения услуги
deadline = models.DateTimeField(null=True, blank=True, default=None)
def priority_up(self):
# ищем услугу с большим приоритетом(число приоритета меньше)
target_abtar = AbonTariff.objects.filter(
abon=self.abon,
tariff_priority__lt=self.tariff_priority
).order_by('-tariff_priority')[:1]
if target_abtar.count() > 0:
target_abtar = target_abtar[0]
else:
return
# Ищем текущий тариф абонента
active_abtar = AbonTariff.objects.filter(
abon=self.abon
)[:1]
if active_abtar.count() > 0:
active_abtar = active_abtar[0]
else:
return
# Если услуга с которой хотим поменяться приоритетом является текущей то нельзя меняться
if active_abtar == target_abtar:
return
# Swap приоритетов у текущего и найденного с меньшим tariff_priority (большим приоритетом)
tmp_prior = target_abtar.tariff_priority
target_abtar.tariff_priority = self.tariff_priority
target_abtar.save(update_fields=['tariff_priority'])
self.tariff_priority = tmp_prior
self.save(update_fields=['tariff_priority'])
def priority_down(self):
# ищем услугу с меньшим приоритетом
target_abtar = AbonTariff.objects.filter(
abon=self.abon,
tariff_priority__gt=self.tariff_priority
)[:1]
if target_abtar.count() > 0:
target_abtar = target_abtar[0]
else:
# меньше нет, это самая последняя услуга
return
# Swap приоритетов у текущего и найденного с большим tariff_priority (меньшим приоритетом)
tmp_pr = self.tariff_priority
self.tariff_priority = target_abtar.tariff_priority
target_abtar.tariff_priority = tmp_pr
target_abtar.save(update_fields=['tariff_priority'])
self.save(update_fields=['tariff_priority'])
# Считает текущую стоимость услуг согласно выбранной для тарифа логики оплаты (см. в документации)
def calc_amount_service(self):
amount = self.tariff.amount
return round(amount, 2)
# Активируем тариф
def activate(self, current_user, deadline=None):
calc_obj = self.tariff.get_calc_type()(self)
amnt = self.tariff.amount
# если не хватает денег
if self.abon.ballance < amnt:
raise LogicError(_('not enough money'))
# считаем дату активации услуги
self.time_start = timezone.now()
# считаем дату завершения услуги
if deadline is None:
self.deadline = calc_obj.calc_deadline()
else:
self.deadline = deadline
# снимаем деньги за услугу
self.abon.make_pay(current_user, amnt)
self.save()
# Используется-ли услуга сейчас, если время старта есть то он активирован
def is_started(self):
return True if self.time_start is not None else False
return False if self.time_start is None else True
def __str__(self):
return "%d: '%s' - '%s'" % (
self.tariff_priority,
self.tariff.title,
self.abon.get_short_name()
return "%d: %s" % (
self.pk or 0,
self.tariff.title
)
class Meta:
ordering = ('tariff_priority',)
db_table = 'abonent_tariff'
unique_together = (('abon', 'tariff', 'tariff_priority'),)
permissions = (
('can_complete_service', _('finish service perm')),
('can_activate_service', _('activate service perm'))
)
@ -199,20 +123,8 @@ 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_tariffs = models.ManyToManyField(Tariff, through=AbonTariff)
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)
ballance = models.FloatField(default=0.0)
ip_address = MyGenericIPAddressField(blank=True, null=True)
@ -220,22 +132,13 @@ 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)
_act_tar_cache = None
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, use_cache=True):
if self._act_tar_cache and use_cache:
return self._act_tar_cache
ats = AbonTariff.objects.filter(abon=self).exclude(time_start=None)
if ats.count() > 0:
self._act_tar_cache = ats[0].tariff
return ats[0].tariff
else:
self._act_tar_cache = None
# возвращает связь с текущим тарифом для абонента
def active_tariff(self):
return self.current_tariff
class Meta:
db_table = 'abonent'
@ -263,23 +166,31 @@ class Abon(UserProfile):
def pick_tariff(self, tariff, author, comment=None, deadline=None):
assert isinstance(tariff, Tariff)
# выбераем связь ТарифАбонент с самым низким приоритетом
abtrf = AbonTariff.objects.filter(abon=self).order_by('-tariff_priority')[:1]
abtrf = abtrf[0] if abtrf.count() > 0 else None
amount = round(tariff.amount, 2)
# создаём новую связь с приоритетом ещё ниже
new_abtar = AbonTariff(
abon=self,
tariff=tariff,
tariff_priority=abtrf.tariff_priority + 1 if abtrf else -1
)
if not author.is_staff and tariff.is_admin:
raise LogicError(_('User that is no staff can not buy admin services'))
# Если это первая услуга в списке (фильтр по приоритету ничего не вернул)
if not abtrf:
# значит пробуем её активировать
new_abtar.activate(author, deadline)
else:
new_abtar.save()
if self.current_tariff is not None:
if self.current_tariff.tariff == tariff:
# Эта услуга уже подключена
raise LogicError(_('That service already activated'))
else:
# Не надо молча заменять услугу если какая-то уже есть
raise LogicError(_('Service already activated'))
# если не хватает денег
if self.ballance < amount:
raise LogicError(_('not enough money'))
new_abtar = AbonTariff(deadline=deadline, tariff=tariff)
new_abtar.save()
self.current_tariff = new_abtar
# снимаем деньги за услугу
self.ballance -= amount
self.save()
# Запись об этом в лог
AbonLog.objects.create(
@ -288,43 +199,23 @@ class Abon(UserProfile):
comment=comment or _('Buy service default log')
)
# Пробует подключить новую услугу если пришло время
def activate_next_tariff(self, author):
ats = AbonTariff.objects.filter(abon=self).order_by('tariff_priority')
nw = timezone.make_aware(datetime.now())
for at in ats:
# услуга не активна, продолжаем
if at.deadline is None:
continue
# если услуга просрочена
if nw > at.deadline:
print(_('service overdue log'))
# выберем следующую по приоритету
# next_tarifs = AbonTariff.objects.filter(tariff_priority__gt = self.tariff_priority, abon=self.abon)
next_tarifs = [tr for tr in ats if tr.tariff_priority > at.tariff_priority][:2]
#next_tarifs = filter(lambda tr: tr.tariff_priority > at.tariff_priority, ats)[:2]
# и если что-нибудь из списка следующих услуг вернулось - то активируем
if len(next_tarifs) > 0:
next_tarifs[0].activate(author)
# удаляем запись о текущей услугу.
at.delete()
return
# Производим расчёт услуги абонента, т.е. завершаем если пришло время
def bill_service(self, author):
abon_tariff = self.active_tariff()
nw = timezone.now()
# если услуга просрочена
if nw > abon_tariff.deadline:
print("Service %s for user %s is overdued, end service" % (abon_tariff.tariff, self))
abon_tariff.delete()
# есть-ли доступ у абонента к услуге, смотрим в tariff_app.custom_tariffs.<TariffBase>.manage_access()
def is_access(self):
ats = AbonTariff.objects.filter(abon=self).exclude(time_start=None)
if not ats or ats.count() < 1:
return False
trf = ats[0].tariff
ct = trf.get_calc_type()(ats[0])
if ct.manage_access(self):
return True
else:
abon_tariff = self.active_tariff()
if abon_tariff is None:
return False
trf = abon_tariff.tariff
ct = trf.get_calc_type()(abon_tariff)
return ct.manage_access(self)
# создаём абонента из структуры агента
def build_agent_struct(self):
@ -332,33 +223,22 @@ class Abon(UserProfile):
user_ip = ip2int(self.ip_address)
else:
return
inst_tariff = self.active_tariff()
if inst_tariff:
agent_trf = TariffStruct(inst_tariff.id, inst_tariff.speedIn, inst_tariff.speedOut)
else:
abon_tariff = self.active_tariff()
if abon_tariff is None:
agent_trf = TariffStruct()
else:
trf = abon_tariff.tariff
agent_trf = TariffStruct(trf.id, trf.speedIn, trf.speedOut)
return AbonStruct(self.pk, user_ip, agent_trf, bool(self.is_active))
def save(self, *args, **kwargs):
# проверяем не-ли у кого такого-же ip
if Abon.objects.filter(ip_address=self.ip_address).exclude(pk=self.pk).count() > 0:
if self.ip_address is not None and Abon.objects.filter(ip_address=self.ip_address).exclude(pk=self.pk).count() > 0:
self.is_bad_ip = True
raise LogicError(_('Ip address already exist'))
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])
@ -437,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
@ -468,40 +348,14 @@ def abon_del_signal(sender, instance, **kwargs):
return True
def abontariff_post_save(sender, instance, **kwargs):
# Тут или подключение абону услуги, или изменение приоритета
if not kwargs['created']:
# если изменение приоритета то не говорим об этом NAS'у
return
if instance.abon.ip_address is None:
return
try:
agent_abon = instance.abon.build_agent_struct()
if agent_abon is None:
return True
tm = Transmitter()
tm.update_user(agent_abon)
except (NasFailedResult, NasNetworkError):
return True
def abontariff_del_signal(sender, instance, **kwargs):
if not instance.is_started():
# если удаляем не активную услугу то говорить об этом NAS'у не обязательно
return
if instance.abon.ip_address is None:
# если у абонента нет ip то и создавать правило не на кого
return
try:
agent_abon = instance.abon.build_agent_struct()
tm = Transmitter()
tm.pause_user(agent_abon)
except (NasFailedResult, NasNetworkError):
return True
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_save.connect(abontariff_post_save, sender=AbonTariff)
models.signals.post_delete.connect(abontariff_del_signal, sender=AbonTariff)
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_tariff">{% 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>

24
abonapp/templates/abonapp/charts.html

@ -3,10 +3,10 @@
{% block content %}
<div class="row">
<div class="col-sm-12">
<div class="col-sm-10">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">График использования</h3>
<h3 class="panel-title">{% trans 'Graph of use' %}</h3>
</div>
<div class="panel-body">
{% if charts_data %}
@ -16,7 +16,6 @@
new Chartist.Line('#chrt', {
series: [
{
name: 'График траффика',
data: [
{{ charts_data }}
]
@ -34,16 +33,31 @@
labelInterpolationFnc: function (value) {
return moment(value).format('HH:mm:ss');
}
}
},
lineSmooth: Chartist.Interpolation.cardinal({
tension: 0
})
});
});
</script>
{% else %}
<h2>Траффик не найден</h2>
<h2>{% trans 'Static info was Not found' %}</h2>
{% endif %}
</div>
</div>
</div>
<div class="col-sm-2">
<div class="panel panel-default">
<div class="panel-heading">{% trans 'Graphs by dates' %}</div>
<div class="list-group">
{% for dat in dates %}
<a href="{% url 'abonapp:charts' abon_group.pk abon.pk %}?wantdate={{ dat|date:'dmY' }}" class="list-group-item{% if dat == now %} active{% endif %}">{{ dat|date:'j E' }}</a>
{% empty %}
{% trans 'Static info was Not found' %}
{% endfor %}
</div>
</div>
</div>
</div>
{% endblock %}

47
abonapp/templates/abonapp/dial_log.html

@ -0,0 +1,47 @@
{% extends request.is_ajax|yesno:'nullcont.htm,abonapp/ext.htm' %}
{% load i18n %}
{% block content %}
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>{% trans 'Play' %}</th>
<th>{% trans 'calldate' %}</th>
<th>{% trans 'src' %}</th>
<th>{% trans 'dst' %}</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 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>
<td>{{ log.calldate|date:'d E Y, H:i:s' }}</td>
<td>{{ log.src }}</td>
<td>{{ log.dst }}</td>
<td>{{ log.duration }}</td>
<td>{{ log.start|date:'d E Y, H:i:s' }}</td>
<td>{{ log.answer|date:'d E Y, H:i:s' }}</td>
<td>{{ log.end|date:'d E Y, H:i:s' }}</td>
<td>{{ log.locate_disposition }}</td>
</tr>
{% empty %}
<tr>
<td colspan="9">{% trans 'Calls was not found' %}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% include 'toolbar_page.html' with pag=logs %}
{% endblock %}

82
abonapp/templates/abonapp/editAbon.html

@ -28,14 +28,21 @@
<div class="form-group-sm">
<label for="id_telephone" class="col-sm-4 control-label">{% trans 'Telephone' %}</label>
<div class="col-sm-8">
{{ form.telephone }}{{ form.telephone.errors }}
<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" title="{% trans 'Call to' %}">
<span class="glyphicon glyphicon-earphone"></span>
</a>
</span>
</div>
</div>
</div>
<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>
@ -106,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">
@ -140,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>

5
abonapp/templates/abonapp/ext.htm

@ -53,6 +53,11 @@
<a href="{{ abtasklog }}">{% trans 'Charts' %}</a>
</li>
{% url 'abonapp:dials' abon_group.pk abon.pk as abdials %}
<li{% if abdials == request.path %} class="active"{% endif %}>
<a href="{{ abdials }}">{% trans 'Dialing' %}</a>
</li>
</ul>
<div class="tab-content">

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>

78
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,57 +46,64 @@
</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>
<th width="150">{% trans 'Telephone' %}</th>
<!--<th width="150">{% trans 'Service' %}</th>-->
<th width="150">{% trans 'Service' %}</th>
<th width="50">
<a href="{% url 'abonapp:people_list' abon_group.pk %}?order_by=ballance&dir={{ dir|default:"down" }}">
{% trans 'Ballance' %}
</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:"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 %}
{ % if perms.tariff_app.change_tariff %}
<a href="{ % url 'tarifs:edit' human.active_tariff.pk %}">{ { human.active_tariff.title }}</a>
{ % else %}
{ { human.active_tariff.title }}
{ % endif %}
{ % else %}&mdash;&mdash;&mdash;
{ % endif %}
</td>-->
<td>
{% if human.active_tariff %}
{% if perms.tariff_app.change_tariff %}
<a href="{% url 'tarifs:edit' human.active_tariff.tariff.pk %}">{{ human.active_tariff.tariff.title }}</a>
{% else %}
{{ human.active_tariff.tariff.title }}
{% endif %}
{% else %}&mdash;&mdash;&mdash;
{% endif %}
</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>
@ -104,7 +112,7 @@
</tr>
{% empty %}
<tr>
<td colspan="10">
<td colspan="11">
{% trans 'Subscribers not found' %}.
{% if perms.abonapp.add_abon %}
<a href="{% url 'abonapp:add_abon' abon_group.pk %}">{% trans 'Add abon' %}</a>
@ -112,10 +120,11 @@
</td>
</tr>
{% endfor %}
{% endwith %}
</tbody>
<tfoot>
<tr>
<td colspan="10" class="btn-group">
<td colspan="11" class="btn-group">
{% if perms.abonapp.add_abon %}
<a href="{% url 'abonapp:add_abon' abon_group.pk %}" class="btn btn-sm btn-default" title="Добавить">
<span class="glyphicon glyphicon-plus"></span> {% trans 'Add abon' %}
@ -134,16 +143,27 @@
</div>
</div>
<div class="col-lg-2 sidebar-offcanvas">
<div class="list-group">
{% for street in streets %}
<a href="{% url 'abonapp:people_list' abon_group.pk %}?street={{ street.pk }}" class="list-group-item{% if street_id == street.pk %} active{% endif %}">{{ street.name }}</a>
{% empty %}
<a href="#" class="list-group-item">{% trans 'No streets found for that group' %}</a>
{% endfor %}
<div class="panel panel-default">
<div class="panel-heading">{% trans 'Streets' %}</div>
<div class="list-group">
{% for street in streets %}
<a href="{% url 'abonapp:people_list' abon_group.pk %}?street={{ street.pk }}" class="list-group-item{% if street_id == street.pk %} active{% endif %}">{{ street.name }}</a>
{% 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>
</div>
{% include 'toolbar_page.html' with pag=peoples %}
{% endblock %}
{% endblock %}

119
abonapp/templates/abonapp/service.html

@ -0,0 +1,119 @@
{% extends request.is_ajax|yesno:'nullcont.htm,abonapp/ext.htm' %}
{% load i18n %}
{% block content %}
<div class="row">
<div class="col-sm-6">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{% trans "Subscriber's service" %}</h3>
</div>
<div class="panel-body">
{% if abon_tariff %}
<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 %}
{% 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>
<div class="col-sm-6">
<div class="panel panel-default">
<div class="panel-heading">
<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>
{% endblock %}

101
abonapp/templates/abonapp/services.html

@ -1,101 +0,0 @@
{% extends request.is_ajax|yesno:'nullcont.htm,abonapp/ext.htm' %}
{% load i18n %}
{% block content %}
<legend>{% trans 'Services of subscriber' %}</legend>
<table class="table table-striped table-bordered">
<thead>
<tr>
<th width="50">{% trans 'Priority' %}</th>
<th>{% trans 'Service' %}</th>
<th>{% trans 'Sum' %}</th>
<th>{% trans 'Input speed' %}</th>
<th>{% trans 'Output speed' %}</th>
<th>{% trans 'Works until' %}</th>
<th>{% trans 'Do' %}</th>
</tr>
</thead>
<tbody>
{% for trf in abon_tarifs %}
<tr{% if trf.id == active_abontariff_id %} class="active"{% endif %}>
<td>{{ trf.tariff_priority }}</td>
<td>
{% if perms.tariff_app.change_tariff %}
<a href="{% url 'tarifs:edit' trf.tariff.id %}" title="{{ trf.time_start|default:'' }}">
{{ trf.tariff.title }}
</a>
{% else %}
{{ trf.tariff.title }}
{% endif %}
</td>
<td>{{ trf.tariff.amount }}</td>
<td>{{ trf.tariff.speedIn }}</td>
<td>{{ trf.tariff.speedOut }}</td>
<td>{{ trf.deadline|date:"d E Y, l" }}</td>
{% if trf.id != active_abontariff_id %}
<td class="btn-group">
{% if perms.abonapp.can_activate_service %}
{% if not active_abontariff_id %}
<a href="{% url 'abonapp:activate_service' abon_group.id abon.id trf.id %}"
class="btn btn-success btn-sm" title="{% trans 'Activate service' %}">
<i class="glyphicon glyphicon-shopping-cart"></i>
</a>
{% endif %}
{% endif %}
<!-- "{ % url 'abonapp:chpriority_tariff' abon_group.id abon.id % }?t={ { trf.id } }&a=up" -->
<a href="#"
class="btn btn-default btn-sm disabled" title="{% trans 'Priority up' %}">
<i class="glyphicon glyphicon-hand-up"></i>
</a>
<!-- "{ % url 'abonapp:chpriority_tariff' abon_group.id abon.id % }?t={ { trf.id } }&a=down" -->
<a href="#"
class="btn btn-default btn-sm disabled" title="{% trans 'Priority down' %}">
<i class="glyphicon glyphicon-hand-down"></i>
</a>
{% if perms.abonapp.delete_abontariff %}
<a href="{% url 'abonapp:unsubscribe_service' abon_group.id abon.id trf.id %}"
class="btn btn-danger btn-sm" title="{% trans 'Delete service' %}">
<i class="glyphicon glyphicon-remove"></i>
</a>
{% endif %}
</td>
{% else %}
<td>
<a href="{% url 'abonapp:compl_srv' abon_group.id abon.id trf.id %}" class="btn btn-danger btn-sm">
<i class="glyphicon glyphicon-remove"></i> {% trans 'Finish service' %}
</a>
</td>
{% endif %}
</tr>
{% empty %}
<tr>
<td colspan="7">{% trans 'Services of subscribers not found' %}.
{% if perms.abonapp.can_buy_tariff %}
<a href="{% url 'abonapp:pick_tariff' abon_group.id abon.id %}" class="lgtbx">{% trans 'Buy' %}</a>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
{% if perms.abonapp.can_buy_tariff %}
<tfoot>
<tr>
<th colspan="7">
<a href="{% url 'abonapp:pick_tariff' abon_group.id abon.id %}" class="btn btn-sm btn-success">
<span class="glyphicon glyphicon-plus"></span> {% trans 'Buy service' %}
</a>
</th>
</tr>
</tfoot>
{% endif %}
</table>
{% endblock %}

225
abonapp/tests.py

@ -1,225 +0,0 @@
from django.shortcuts import resolve_url
from django.test import TestCase
from django.test.client import Client
from agent import NasNetworkError
from .models import AbonTariff, Abon, AbonGroup, AbonRawPassword
from tariff_app.models import Tariff
from mydefs import LogicError
class AbonTestCase(TestCase):
def setUp(self):
try:
Tariff.objects.create(
title='test_tariff',
descr='taroff descr',
speedIn=1.2,
speedOut=3.0,
amount=3
)
abon = Abon()
abon.username = '1234567'
abon.fio = 'mainuser'
abon.telephone = '+79788328884'
abon.set_password('ps')
abon.is_superuser = True
abon.save()
abon_group = AbonGroup.objects.create(title='abon_group')
abon_group.profiles.add(abon)
except NasNetworkError:
pass
# проверка на пополнение счёта
def test_add_ballance(self):
try:
abon = Abon.objects.get(username='1234567')
ballance = abon.ballance
abon.add_ballance(abon, 13, 'test pay')
abon.save(update_fields=['ballance'])
self.assertEqual(abon.ballance, ballance+13)
ballance = abon.ballance
abon.add_ballance(abon, 5.34, 'test float pay')
abon.save(update_fields=['ballance'])
self.assertEqual(abon.ballance, ballance+5.34)
except NasNetworkError:
pass
# пробуем выбрать услугу
def test_pick_tariff(self):
try:
tariff = Tariff.objects.get(title='test_tariff')
abon = Abon.objects.get(username='1234567')
try:
abon.pick_tariff(tariff, abon)
# нет денег, должно всплыть исключение и сюда дойти мы не должны
self.assertFalse(True)
except LogicError:
pass
act_tar = abon.active_tariff()
# если недостаточно денег на счету
assert abon.ballance <= tariff.amount
# У абонента на счету 0, не должна быть куплена услуга
self.assertEqual(act_tar, None)
# Раз услуги нет то и доступа быть не должно
self.assertTrue(not abon.is_access())
# с деньгами
abon.add_ballance(abon, 7.34, 'add pay for test pick tariff')
abon.pick_tariff(tariff, abon)
act_tar = abon.active_tariff()
# должны получить указанную услугу
self.assertEqual(act_tar, tariff)
# и получить доступ
self.assertTrue(abon.is_access())
except NasNetworkError:
pass
# тестим очередь услуг
def test_services_queue(self):
abon = Abon.objects.get(username='1234567')
tariff = Tariff.objects.get(title='test_tariff')
abon.add_ballance(abon, 9, 'add pay for test services queue')
abon.save()
abon.pick_tariff(tariff, abon)
abon.pick_tariff(tariff, abon)
abon.pick_tariff(tariff, abon)
# снять деньги должно было только за первый выбор, остальные стают в очередь услуг
self.assertEqual(abon.ballance, 6)
c = Client()
# login
c.post(resolve_url('acc_app:login'), {'login': '1234567', 'password': 'ps'})
url = resolve_url('abonapp:compl_srv', gid=1, uid=1, srvid=1)
resp = c.get(url)
print('RESP:', resp)
self.assertEqual(resp.status_code, 200)
resp = c.post(url, data={
'finish_confirm': 'yes'
})
print('RESP:', resp)
# при успешной остановке услуги идёт редирект на др страницу
self.assertEqual(resp.status_code, 302)
# текущей услуги быть не должно
act_tar = abon.active_tariff()
self.assertIsNone(act_tar)
# не активных услуг останется 2
noact_count = AbonTariff.objects.filter(abon=abon).filter(time_start=None).count()
self.assertEqual(noact_count, 2)
# проверяем платёжку alltime
def test_allpay(self):
from hashlib import md5
from djing.settings import pay_SECRET, pay_SERV_ID
import xmltodict
def sig(act, pay_account, pay_id):
md = md5()
s = '_'.join((str(act), str(pay_account), pay_SERV_ID, str(pay_id), pay_SECRET))
md.update(bytes(s, 'utf-8'))
return md.hexdigest()
c = Client()
url = resolve_url('abonapp:terminal_pay')
r = c.get(url, {
'ACT': 1, 'PAY_ACCOUNT': '1234567',
'SERVICE_ID': pay_SERV_ID,
'PAY_ID': 3561234,
'TRADE_POINT': 377,
'SIGN': sig(1, 1234567, 3561234)
})
xobj = xmltodict.parse(r.content)
self.assertEqual(int(xobj['pay-response']['status_code']), 21)
r = c.get(url, {
'ACT': 4, 'PAY_ACCOUNT': '1234567',
'SERVICE_ID': pay_SERV_ID,
'PAY_ID': 3561234,
'PAY_AMOUNT': 1.0,
'TRADE_POINT': 377,
'SIGN': sig(4, 1234567, 3561234)
})
xobj = xmltodict.parse(r.content)
self.assertEqual(int(xobj['pay-response']['status_code']), 22)
r = c.get(url, {
'ACT': 4, 'PAY_ACCOUNT': '1234567',
'SERVICE_ID': pay_SERV_ID,
'PAY_ID': 3561234,
'PAY_AMOUNT': 1.0,
'TRADE_POINT': 377,
'SIGN': sig(4, 1234567, 3561234)
})
xobj = xmltodict.parse(r.content)
self.assertEqual(int(xobj['pay-response']['status_code']), -100)
r = c.get(url, {
'ACT': 7, 'PAY_ACCOUNT': '1234567',
'SERVICE_ID': pay_SERV_ID,
'PAY_ID': 3561234,
'PAY_AMOUNT': 1.0,
'TRADE_POINT': 377,
'SIGN': sig(7, 1234567, 3561234)
})
xobj = xmltodict.parse(r.content)
self.assertEqual(int(xobj['pay-response']['status_code']), 11)
abon = Abon.objects.get(username='1234567')
self.assertEqual(abon.ballance, 1)
# пробуем добавить группу абонентов
def test_add_abongroup(self):
abon = Abon.objects.get(username='1234567')
ag = AbonGroup.objects.create(title='%&34%$&*(')
ag.profiles.add(abon)
# пробуем добавить абонента
def test_add_abon(self):
c = Client()
c.login(username='1234567', password='ps')
url = resolve_url('abonapp:add_abon', gid=1)
r = c.get(url)
# поглядим на страницу добавления абонента
self.assertEqual(r.status_code, 200)
r = c.post(url, {
'username': '123',
'password': 'ps',
'fio': 'Abon Fio',
'telephone': '+79783753914',
'is_active': True
})
self.assertEqual(r.status_code, 302)
r = c.get(resolve_url('abonapp:add_abon', gid=324))
self.assertEqual(r.status_code, 404)
try:
abn = Abon.objects.get(username='123')
self.assertIsNotNone(abn)
psw = AbonRawPassword.objects.get(account=abn, passw_text='ps')
self.assertIsNotNone(psw)
except Abon.DoesNotExist:
# абонент должен был создаться
self.assertTrue(False)
except AbonRawPassword.DoesNotExist:
# должен быть пароль абонента простым текстом
self.assertTrue(False)
# пробуем удалить абонента
def test_view_delentity(self):
c = Client()
c.login(username='1234567', password='ps')
url = resolve_url('abonapp:del_abon') + '?t=a&id=1'
r = c.get('/abons/1/addabon')
class AbonTariffTestCase(TestCase):
def setUp(self):
abon = Abon.objects.create(
username='1234567',
telephone='+79788328884'
)
tariff = Tariff.objects.create(
title='test_tariff',
descr='taroff descr',
speedIn=1.2,
speedOut=3.0,
amount=3
)
AbonTariff.objects.create(
abon=abon,
tariff=tariff
)

12
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'),
@ -15,21 +18,20 @@ urlpatterns = [
url(r'^(?P<uid>\d+)/addinvoice$', views.add_invoice, name='add_invoice'),
url(r'^(?P<uid>\d+)/pick$', views.pick_tariff, name='pick_tariff'),
url(r'^(?P<uid>\d+)/chpriority$', views.chpriority, name='chpriority_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+)/activate_service(?P<srvid>\d+)$', views.activate_service, name='activate_service'),
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')
]

330
abonapp/views.py

@ -3,7 +3,8 @@ from json import dumps
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
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,14 +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 datetime import datetime
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
@ -31,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:
@ -42,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)
@ -189,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:
@ -242,16 +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)
abon_tarifs = models.AbonTariff.objects.filter(abon=uid)
active_abontariff = abon_tarifs.exclude(time_start=None)
return render(request, 'abonapp/services.html', {
return render(request, 'abonapp/service.html', {
'abon': abon,
'abon_tarifs': abon_tarifs,
'active_abontariff_id': active_abontariff[0].id if active_abontariff.count() > 0 else None,
'abon_group': abon.group
'abon_tariff': abon.current_tariff,
'abon_group': abon.group,
'services': grp.tariffs.all()
})
@ -262,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:
@ -283,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
@ -293,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)
@ -306,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', {
@ -318,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)
@ -397,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)
@ -428,34 +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
@mydefs.only_admins
def chpriority(request, gid, uid):
t = request.GET.get('t')
act = request.GET.get('a')
current_abon_tariff = get_object_or_404(models.AbonTariff, pk=t)
try:
if act == 'up':
current_abon_tariff.priority_up()
elif act == 'down':
current_abon_tariff.priority_down()
except (NasFailedResult, NasNetworkError) as e:
messages.error(request, e)
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)
@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
@ -513,44 +454,11 @@ def complete_service(request, gid, uid, srvid):
})
@login_required
@permission_required('abonapp.can_activate_service')
def activate_service(request, gid, uid, srvid):
abtar = get_object_or_404(models.AbonTariff, pk=srvid)
amount = abtar.calc_amount_service()
try:
if request.method == 'POST':
if request.POST.get('finish_confirm') != 'yes':
return HttpResponse(_('Not confirmed'))
abtar.activate(request.user)
messages.success(request, _('Service has been activated successfully'))
return redirect('abonapp:abon_services', gid, uid)
except (NasFailedResult, mydefs.LogicError) as e:
messages.error(request, e)
except NasNetworkError as e:
messages.warning(request, e)
except mydefs.MultipleException as errs:
for err in errs.err_list:
messages.add_message(request, messages.constants.ERROR, err)
calc_obj = abtar.tariff.get_calc_type()(abtar)
return render(request, 'abonapp/activate_service.html', {
'abon': abtar.abon,
'abon_group': abtar.abon.group,
'abtar': abtar,
'amount': amount,
'diff': abtar.abon.ballance - amount,
'deadline': calc_obj.calc_deadline()
})
@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)
@ -559,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
@ -610,7 +518,6 @@ def update_nas(request, group_id):
@login_required
@mydefs.only_admins
def task_log(request, gid, uid):
from taskapp.models import Task
abon = get_object_or_404(models.Abon, pk=uid)
tasks = Task.objects.filter(abon=abon)
return render(request, 'abonapp/task_log.html', {
@ -626,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:
@ -672,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,
@ -702,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'))
@ -714,14 +621,16 @@ def clear_dev(request, gid, uid):
@login_required
@mydefs.only_admins
def charts(request, gid, uid):
from statistics.models import getModel
from datetime import datetime, date, time, timedelta
high = 100
def byte_to_mbit(x):
return ((x/60)*8)/2**20
wandate = request.GET.get('wantdate')
if wandate:
wandate = datetime.strptime(wandate, '%d%m%Y').date()
else:
wandate = date.today()
try:
StatElem = getModel()
StatElem = getModel(wandate)
abon = models.Abon.objects.get(pk=uid)
if abon.group is None:
abon.group = models.AbonGroup.objects.get(pk=gid)
@ -731,20 +640,18 @@ def charts(request, gid, uid):
if abon.ip_address is None:
charts_data = None
else:
charts_data = StatElem.objects.filter(ip=abon.ip_address)
#oct_limit = StatElem.percentile([cd.octets for cd in charts_data], 0.05)
# ниже возвращаем пары значений трафика который переведён в mByte, и unix timestamp
midnight = datetime.combine(date.today(), time.min)
charts_data = [(cd.cur_time.timestamp()*1000, byte_to_mbit(cd.octets)) for cd in charts_data]
if len(charts_data) > 0:
charts_data.append( (charts_data[-1:][0][0], 0.0) )
charts_data = ["{x: new Date(%d), y: %.2f}" % (cd[0], cd[1]) for cd in charts_data]
charts_data.append("{x:new Date(%d),y:0}" % (int((midnight + timedelta(days=1)).timestamp()) * 1000))
charts_data = StatElem.objects.chart(
abon.ip_address,
count_of_parts=30,
want_date=wandate
)
abontariff = abon.active_tariff()
high = abontariff.speedIn + abontariff.speedOut
if high > 100:
high = 100
if abontariff is not None:
trf = abontariff.tariff
high = trf.speedIn + trf.speedOut
if high > 100:
high = 100
except models.Abon.DoesNotExist:
messages.error(request, _('Abon does not exist'))
@ -760,7 +667,8 @@ def charts(request, gid, uid):
'abon_group': abongroup,
'abon': abon,
'charts_data': ',\n'.join(charts_data) if charts_data is not None else None,
'high': high
'high': high,
'dates': get_dates()
})
@ -799,7 +707,6 @@ def make_extra_field(request, gid, uid):
@permission_required('abonapp.change_extra_fields_model')
def extra_field_change(request, gid, uid):
extras = [(int(x), y) for x, y in zip(request.POST.getlist('ed'), request.POST.getlist('ex'))]
print(extras)
try:
for ex in extras:
extra_field = models.ExtraFieldsModel.objects.get(pk=ex[0])
@ -860,12 +767,115 @@ def abon_ping(request):
}))
@login_required
@mydefs.only_admins
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):
return redirect('abonapp:dials', abon.group.pk, abon.pk)
if abon.telephone is not None and abon.telephone != '':
tel = abon.telephone.replace('+', '')
logs = AsteriskCDR.objects.filter(
Q(src__contains=tel) | Q(dst__contains=tel)
)
logs = mydefs.pag_mn(request, logs)
else:
logs = None
return render(request, 'abonapp/dial_log.html', {
'logs': logs,
'abon_group': get_object_or_404(models.AbonGroup, pk=gid),
'abon': abon
})
@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):
ablist = [{
'id': abn.pk,
'tarif_id': abn.active_tariff().pk if abn.active_tariff() else 0,
'tarif_id': abn.active_tariff().tariff.pk if abn.active_tariff() is not None else 0,
'ip': abn.ip_address.int_ip(),
'is_active': abn.is_active
} for abn in models.Abon.objects.all()]
@ -887,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}$')]
)

4
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 %}
{% 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

50
agent/commands/dhcp.py

@ -0,0 +1,50 @@
# -*- coding: utf-8 -*-
from django.core.exceptions import MultipleObjectsReturned
from django.utils.translation import ugettext as _
from abonapp.models import Abon
from devapp.models import Device, Port
def dhcp_commit(client_ip, client_mac, switch_mac, switch_port):
try:
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('S:', _("Ip address:'%s' update for '%s' successfull, on port: %s") % (client_ip, abon.get_short_name(), port))
except Abon.DoesNotExist:
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):
try:
abon = Abon.objects.get(ip_address=client_ip)
abon.ip_address = None
abon.is_dhcp = True
abon.save(update_fields=['ip_address'])
except Abon.DoesNotExist:
pass
def dhcp_release(client_ip):
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

4
bugs.txt

@ -8,3 +8,7 @@
- Не надо коннектиться к микротику когда не собираемся ничего изменять. А то при сохранении залогинились и вышли без действий
- Не удаляет просроченные услуги если не пингуется NAS
- Надо отменить учёт временной зоны
!!! Обязательно проверить как отрабатывает на 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')
]

118
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,32 +30,28 @@ 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,
'current_service': current_service
'current_service': current_service.tariff if current_service is not None else None
}, request=request)
except LogicError as e:
messages.error(request, e)
@ -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()

2
cron.py

@ -16,7 +16,7 @@ def main():
for user in users:
try:
# бдим за услугами абонента: просроченные отключить, заказанные подключить
user.activate_next_tariff(user)
user.bill_service(user)
# если нет ip то и нет смысла лезть в NAS
if user.ip_address is None:

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)

164
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.
@ -8,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-02-27 12:27+0300\n"
"POT-Creation-Date: 2017-05-22 11:59+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Dmitry Novikov nerosketch@gmail.com\n"
"Language: ru\n"
@ -19,20 +18,36 @@ msgstr ""
"%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n"
"%100>=11 && n%100<=14)? 2 : 3);\n"
#: devapp/dev_types.py:54
msgid "DLink switch"
msgstr "Свич D'Link"
#: devapp/dev_types.py:70
msgid "does not fetch the name"
msgstr "не получил имя"
#: devapp/dev_types.py:72
msgid "does not fetch the mac"
msgstr "не нашёл мак"
#: devapp/dev_types.py:116
msgid "PON OLT"
msgstr "PON OLT голова"
#: devapp/templates/devapp/add_dev.html:7
#: devapp/templates/devapp/devices.html:7
#: devapp/templates/devapp/devices_null_group.html:7
#: 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;"
@ -68,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"
@ -102,7 +117,7 @@ msgstr "Устройства без группы"
#: devapp/templates/devapp/group_list.html:18
msgid "Group title"
msgstr ""
msgstr "Название"
#: devapp/templates/devapp/group_list.html:28
#, fuzzy
@ -112,7 +127,30 @@ 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 "Имя"
#: devapp/templates/devapp/olt.html:15
msgid "Distance(m)"
msgstr "Расстояние (м)"
#: devapp/templates/devapp/olt.html:16
msgid "Signal"
msgstr "Ур. сигнала"
#: devapp/templates/devapp/olt.html:34
msgid "Ports not found"
msgstr "Порты не найдены"
#: devapp/templates/devapp/ports.html:9
msgid "Title of the type of switch"
@ -122,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 "Инфа о портах не получена"
@ -170,47 +219,84 @@ msgstr "Инфа о точке сохранена"
msgid "Form is invalid, check fields and try again"
msgstr "Ошибка в данных, проверте их ещё раз"
#: devapp/views.py:112 devapp/views.py:140
#: devapp/views.py:114 devapp/views.py:144
msgid "Not Set snmp device password"
msgstr "Не указан snmp пароль для устройства"
#: devapp/views.py:114 devapp/views.py:142
#: devapp/views.py:116 devapp/views.py:146
msgid "Dot was not pinged"
msgstr "Эта точка не пингуется"
#: devapp/views.py:116
#: devapp/views.py:118
msgid "wait for a reply from the SNMP Timeout"
msgstr "Время ожидания ответа от SNMP истекло"
msgid "Ports"
msgstr "Порты"
#: devapp/views.py:120
msgid "SNMP error on device"
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 "ONU Голова"
msgstr "Онушка"
msgid "does not fetch the name"
msgstr "не получил имя"
msgid "Are you sure that you want to delete switch port from db?"
msgstr "Вы уверены что хотите удалить порт свича из бд?"
msgid "does not fetch the mac"
msgstr "не нашёл мак"
msgid "Port successfully saved"
msgstr "Порт успешно сохранён"
msgid "Ports not found"
msgstr "Онушки не получил"
msgid "Port number on device must be unique"
msgstr "Номер порта на устройстве должен быть уникальным"
msgid "DLink switch"
msgstr "Свич D'Link"
msgid "Mac address is required for fill"
msgstr "MAC-адрес необходим для заполнения"
msgid "Mac"
msgstr "Мак"
msgid "Device with that mac is already exist"
msgstr "Устройство с этим мак-адресом уже есть"
msgid "Name"
msgstr "Имя"
msgid "Parent device"
msgstr "Родительское устройство"
msgid "Distance(m)"
msgstr "Расстояние (м)"
msgid "Attached user"
msgstr "Прикрепленный абонент"
msgid "Signal"
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

24
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>
@ -89,4 +107,4 @@
</div>
</div>
{% endblock %}
{% endblock %}

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>

38
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' %}
@ -77,4 +111,4 @@
</div>
</div>
{% endblock %}
{% endblock %}

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>

11
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>
@ -42,4 +47,4 @@
</div>
</div>
{% endblock %}
{% endblock %}

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

73
dhcp_lever.py

@ -1,13 +1,7 @@
#!/usr/bin/env python3
import os
import sys
import django
from django.core.exceptions import MultipleObjectsReturned, ValidationError
from django.utils.translation import ugettext as _
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djing.settings")
django.setup()
from agent import NasFailedResult, NasNetworkError
from abonapp.models import Abon, Opt82
from redis import Redis
from rq import Queue
def die(text):
@ -15,64 +9,17 @@ def die(text):
exit(1)
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
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)
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())
except Abon.DoesNotExist:
print('ERROR: abon with option82(%s-%d) does not exist' % (opt82.mac, opt82.port))
def dhcp_expiry(client_ip):
try:
abon = Abon.objects.get(ip_address=client_ip)
abon.ip_address = None
abon.is_dhcp = True
abon.save(update_fields=['ip_address'])
except Abon.DoesNotExist:
pass
def dhcp_release(client_ip):
dhcp_expiry(client_ip)
def main(argv):
if __name__ == "__main__":
argv = sys.argv
if len(argv) < 3:
die(_('Too few arguments, exiting...'))
die('Too few arguments, exiting...')
action = argv[1]
q = Queue(connection=Redis())
if action == 'commit':
if len(argv) < 6:
die(_('Too few arguments, exiting...'))
dhcp_commit(argv[2], argv[3], argv[4], int(argv[5]))
die('Too few arguments, exiting...')
q.enqueue('agent.commands.dhcp.dhcp_commit', argv[2], argv[3], argv[4], int(argv[5]))
elif action == 'expiry':
dhcp_expiry(argv[2])
q.enqueue('agent.commands.dhcp.dhcp_expiry', argv[2])
elif action == 'release':
dhcp_release(argv[2])
if __name__ == "__main__":
try:
main(sys.argv)
except (NasNetworkError, NasFailedResult) as e:
print('NAS:', e)
except (ValidationError, ValueError) as e:
print('ERROR:', e)
q.enqueue('agent.commands.dhcp.dhcp_release', argv[2])

0
dialing_app/__init__.py

3
dialing_app/admin.py

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

5
dialing_app/apps.py

@ -0,0 +1,5 @@
from django.apps import AppConfig
class DialingAppConfig(AppConfig):
name = 'dialing_app'

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

@ -0,0 +1,108 @@
# 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.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-05-22 11:59+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Dmitry Novikov nerosketch@gmail.com\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
"%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n"
"%100>=11 && n%100<=14)? 2 : 3);\n"
#: dialing_app/models.py:8 dialing_app/models.py:43
msgid "No answer"
msgstr "Не отвечен"
#: dialing_app/models.py:9 dialing_app/models.py:45
msgid "Failed"
msgstr "С ошибкой"
#: dialing_app/models.py:10 dialing_app/models.py:47
msgid "Busy"
msgstr "Занято"
#: dialing_app/models.py:11 dialing_app/models.py:49
msgid "Answered"
msgstr "Отвечен"
#: dialing_app/models.py:12 dialing_app/models.py:51
msgid "Unknown"
msgstr "Не определён"
#: dialing_app/templates/index.html:9
msgid "Dialing"
msgstr "Звонки"
#: dialing_app/templates/index.html:12
msgid "Last calls"
msgstr "Последние звонки"
#: dialing_app/templates/index.html:20
msgid "Play"
msgstr "Слушать"
#: dialing_app/templates/index.html:21
msgid "calldate"
msgstr "дата звонка"
#: dialing_app/templates/index.html:22
msgid "src"
msgstr "кто"
#: dialing_app/templates/index.html:23
msgid "dst"
msgstr "куда"
#: dialing_app/templates/index.html:24
msgid "duration"
msgstr "продолжительность"
#: dialing_app/templates/index.html:25
msgid "start"
msgstr "начало"
#: dialing_app/templates/index.html:26
msgid "answer"
msgstr "ответ"
#: dialing_app/templates/index.html:27
msgid "end"
msgstr "конец"
#: dialing_app/templates/index.html:28
msgid "disposition"
msgstr "состояние"
#: dialing_app/templates/index.html:50
msgid "Calls was not found"
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 "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',
},
),
]

0
dialing_app/migrations/__init__.py

63
dialing_app/models.py

@ -0,0 +1,63 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
from djing import settings
class AsteriskCDR(models.Model):
DISPOSITION_CHOICES = (
('NO ANSWER', _('No answer')),
('FAILED', _('Failed')),
('BUSY', _('Busy')),
('ANSWERED', _('Answered')),
('UNKNOWN', _('Unknown'))
)
calldate = models.DateTimeField(default='0000-00-00 00:00:00', primary_key=True)
clid = models.CharField(max_length=80, default='')
src = models.CharField(max_length=80, default='')
dst = models.CharField(max_length=80, default='')
dcontext = models.CharField(max_length=80, default='')
channel = models.CharField(max_length=80, default='')
dstchannel = models.CharField(max_length=80, default='')
lastapp = models.CharField(max_length=80, default='')
lastdata = models.CharField(max_length=80, default='')
duration = models.IntegerField(default=0)
billsec = models.IntegerField(default=0)
start = models.DateTimeField(null=True, blank=True, default=None)
answer = models.DateTimeField(null=True, blank=True, default=None)
end = models.DateTimeField(null=True, blank=True, default=None)
disposition = models.CharField(max_length=45, choices=DISPOSITION_CHOICES, default='')
amaflags = models.IntegerField(default=0)
accountcode = models.CharField(max_length=20, default='')
userfield = models.CharField(max_length=255, default='')
uniqueid = models.CharField(max_length=32, default='')
def save(self, *args, **kwargs):
return
def delete(self, *args, **kwargs):
return
def locate_disposition(self):
dsp = self.disposition
if dsp == 'NO ANSWER':
return _('No answer')
elif dsp == 'FAILED':
return _('Failed')
elif dsp == 'BUSY':
return _('Busy')
elif dsp == 'ANSWERED':
return _('Answered')
elif dsp == 'UNKNOWN':
return _('Unknown')
return ''
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 %}

53
dialing_app/templates/index.html

@ -0,0 +1,53 @@
{% 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 'dst' %}</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 log in logs %}
<tr>
<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>
<td>{{ log.dst|abon_if_telephone|safe }}</td>
<td>{{ log.duration }}</td>
<td>{{ log.start|date:'d E Y, H:i:s' }}</td>
<td>{{ log.answer|date:'d E Y, H:i:s' }}</td>
<td>{{ log.end|date:'d E Y, H:i:s' }}</td>
<td>{{ log.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=logs %}
{% endblock %}

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 %}

0
dialing_app/templatetags/__init__.py

20
dialing_app/templatetags/telephone_filters.py

@ -0,0 +1,20 @@
import re
from django import template
from django.shortcuts import resolve_url
from django.template.defaultfilters import stringfilter
register = template.Library()
@register.filter
@stringfilter
def abon_if_telephone(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)
return a
else:
return value

3
dialing_app/tests.py

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

9
dialing_app/urls.py

@ -0,0 +1,9 @@
from django.conf.urls import url
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'^voicemail$', views.vmail, name='vmail')
]

49
dialing_app/views.py

@ -0,0 +1,49 @@
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from django.shortcuts import render, redirect
from django.utils.translation import ugettext_lazy as _
from abonapp.models import Abon
from mydefs import only_admins, pag_mn
from .models import AsteriskCDR
@login_required
@only_admins
def home(request):
logs = AsteriskCDR.objects.exclude(userfield='request').order_by('-calldate')
logs = pag_mn(request, logs)
title = _('Last calls')
return render(request, 'index.html', {
'logs': logs,
'title': title
})
@login_required
@only_admins
def to_abon(request, tel):
abon = Abon.objects.filter(telephone=tel)
abon_count = abon.count()
if abon_count > 1:
messages.warning(request, _('Multiple users with the telephone number'))
elif abon_count == 0:
messages.error(request, _('User with the telephone number not found'))
return redirect('dialapp:home')
abon = abon[0]
if abon.group:
return redirect('abonapp:abon_home', gid=abon.group.pk, uid=abon.pk)
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',
)

11
djing/settings_example.py

@ -37,7 +37,8 @@ INSTALLED_APPS = [
'taskapp',
'clientsideapp',
'chatbot',
'django_messages'
'django_messages',
'dialing_app'
]
MIDDLEWARE_CLASSES = [
@ -123,7 +124,7 @@ USE_I18N = True
USE_L10N = False
USE_TZ = True
USE_TZ = False
DEFAULT_FROM_EMAIL = 'nerosketch@gmail.com'
@ -159,3 +160,9 @@ PAGINATION_ITEMS_PER_PAGE=10
pay_SERV_ID = '<service id>'
pay_SECRET = '<secret>'
DIALING_MEDIA = 'path/to/asterisk_records'
DHCP_TIMEOUT = 14400
DEFAULT_SNMP_PASSWORD = 'public'

1
djing/urls.py

@ -17,6 +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'^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)
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)

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

Loading…
Cancel
Save