Browse Source

Make tests and merge with devel

devel
Dmitry Novikov 8 years ago
parent
commit
2875aabfa3
  1. 5
      README.md
  2. 3
      abonapp/locale/ru/LC_MESSAGES/django.po
  3. 8
      abonapp/templates/abonapp/buy_tariff.html
  4. 3
      abonapp/templates/abonapp/editAbon.html
  5. 4
      abonapp/templates/abonapp/group_list.html
  6. 184
      abonapp/tests.py
  7. 2
      abonapp/urls.py
  8. 52
      abonapp/views.py
  9. 2
      accounts_app/models.py
  10. 4
      agent/core.py
  11. 68
      agent/mod_mikrotik.py
  12. 8
      agent/structs.py
  13. 16
      agent/utils.py
  14. 19
      chatbot/email_bot.py
  15. 5
      chatbot/send_func.py
  16. 9
      devapp/dev_types.py
  17. 13
      devapp/views.py
  18. 2
      dhcp_lever.py
  19. 12
      djing/fields.py
  20. 23
      djing/lib/tln/tln.py
  21. 7
      djing/local_settings.py.template
  22. 14
      djing/settings.py
  23. 2
      docs/dev.md
  24. 28
      docs/dhcp.md
  25. 20
      docs/extra_func.md
  26. 5
      docs/install.md
  27. 2
      docs/map.md
  28. 3
      docs/netflow.md
  29. 14
      docs/user_page.md
  30. 6
      docs/views.md
  31. 1
      group_app/views.py
  32. 2
      msg_app/models.py
  33. 6
      periodic.py
  34. 14
      tariff_app/custom_tariffs.py
  35. 3
      tariff_app/locale/ru/LC_MESSAGES/django.po
  36. 4
      tariff_app/views.py
  37. 2
      taskapp/handle.py
  38. 19
      taskapp/models.py

5
README.md

@ -11,7 +11,10 @@ P.S. Возможно понадобится **Python 3.5** и выше из-з
## Содержание ## Содержание
* [Установка](./docs/install.md) * [Установка](./docs/install.md)
* [Сервисы и API](./docs/services.md) * [Сервисы и API](./docs/services.md)
* [Разработка расширений](./docs/dev.md)
* [Менеджеры устройств](./docs/dev.md)
* [Сбор информации трафика по netflow](./docs/netflow.md) * [Сбор информации трафика по netflow](./docs/netflow.md)
* [Работа с представлениями](./docs/views.md) * [Работа с представлениями](./docs/views.md)
* [Карта](./docs/map.md) * [Карта](./docs/map.md)
* [DHCP](./docs/dhcp.md)
* [Страница абонента](./docs/user_page.md)
* [Дополнительный функционал](./docs/extra_func.md)

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

@ -1157,3 +1157,6 @@ msgstr "История задач"
msgid "Charts" msgid "Charts"
msgstr "Графики" msgstr "Графики"
msgid "Export vCards"
msgstr "Экспорт в vCards"

8
abonapp/templates/abonapp/buy_tariff.html

@ -32,9 +32,9 @@
<select class="form-control" name="tariff" id="id_tariffs"> <select class="form-control" name="tariff" id="id_tariffs">
{% for trf in tariffs %} {% for trf in tariffs %}
{% if trf.pk == selected_tariff %} {% if trf.pk == selected_tariff %}
<option value="{{ trf.pk }}" data-deadline='{{ trf.calc_deadline|date:"Y-m-d" }}' selected>
<option value="{{ trf.pk }}" data-deadline='{{ trf.calc_deadline|date:"Y-m-d H:i:s" }}' selected>
{% else %} {% else %}
<option value="{{ trf.pk }}" data-deadline='{{ trf.calc_deadline|date:"Y-m-d" }}'>
<option value="{{ trf.pk }}" data-deadline='{{ trf.calc_deadline|date:"Y-m-d H:i:s" }}'>
{% endif %} {% endif %}
{{ trf.title }}. {{ trf.amount }}{% trans 'currency' %} (Вх:{{ trf.speedIn }}MBit/s. Исх:{{ trf.speedOut }} MBit/s) {{ trf.title }}. {{ trf.amount }}{% trans 'currency' %} (Вх:{{ trf.speedIn }}MBit/s. Исх:{{ trf.speedOut }} MBit/s)
</option> </option>
@ -44,11 +44,11 @@
{% if not abon.active_tariff %} {% if not abon.active_tariff %}
<div class="input-group"> <div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-calendar"></span></span> <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" }}">
<input type="text" class="form-control" name="deadline" id="id_deadline" value="{{ tariffs.0.calc_deadline|date:'Y-m-d H:i:s' }}">
<script type="text/javascript"> <script type="text/javascript">
$(function () { $(function () {
$('#id_deadline').datetimepicker({ $('#id_deadline').datetimepicker({
format: 'YYYY-MM-DD'
format: 'YYYY-MM-DD HH:mm:ss'
}); });
$('#id_tariffs').on('change', function(){ $('#id_tariffs').on('change', function(){
var a = $(this).find('option:selected'); var a = $(this).find('option:selected');

3
abonapp/templates/abonapp/editAbon.html

@ -147,7 +147,8 @@
<div class="col-sm-8 btn-group btn-group-sm"> <div class="col-sm-8 btn-group btn-group-sm">
{% if device %} {% if device %}
<a href="{% url 'devapp:view' group.pk device.pk %}" target="_blank" class="btn btn-sm btn-default" title="{% trans 'Mac Address' %}: {{ device.mac_addr|default:_('Not assigned') }}"> <a href="{% url 'devapp:view' group.pk device.pk %}" target="_blank" class="btn btn-sm btn-default" title="{% trans 'Mac Address' %}: {{ device.mac_addr|default:_('Not assigned') }}">
<span class="glyphicon glyphicon-hdd"></span> <span class="hidden-md">{{ device.comment|truncatechars:11 }} {{ device.ip_address|default:'' }}</span>
<span class="glyphicon glyphicon-hdd"></span>
<span class="hidden-md">{{ device.comment|truncatechars:11 }} {{ device.ip_address|default:'' }}</span>
</a> </a>
<a href="{% url 'abonapp:clear_dev' group.pk abon.username %}" class="btn btn-sm btn-danger"> <a href="{% url 'abonapp:clear_dev' group.pk abon.username %}" class="btn btn-sm btn-danger">
<span class="glyphicon glyphicon-remove-circle"></span> <span class="hidden-xs hidden-lg">{% trans 'Remove clutch' %}</span> <span class="glyphicon glyphicon-remove-circle"></span> <span class="hidden-xs hidden-lg">{% trans 'Remove clutch' %}</span>

4
abonapp/templates/abonapp/group_list.html

@ -68,6 +68,10 @@
<span class="glyphicon glyphicon-usd"></span> <span class="hidden-xs">{% trans 'Fin report' %}</span> <span class="glyphicon glyphicon-usd"></span> <span class="hidden-xs">{% trans 'Fin report' %}</span>
</a> </a>
{% endif %} {% endif %}
<a href="{% url 'abonapp:vcards' %}" target="_blank" class="btn btn-default">
<span class="glyphicon glyphicon-phone"></span>
<span class="hidden-xs">{% trans 'Export vCards' %}</span>
</a>
</td> </td>
</tr> </tr>
</tfoot> </tfoot>

184
abonapp/tests.py

@ -1,3 +1,4 @@
from abc import ABCMeta
from hashlib import md5 from hashlib import md5
from datetime import date from datetime import date
@ -6,12 +7,15 @@ from django.shortcuts import resolve_url
from django.test import TestCase, RequestFactory from django.test import TestCase, RequestFactory
from django.conf import settings from django.conf import settings
from django.utils import timezone from django.utils import timezone
from django.utils.html import escape
from django.utils.translation import gettext_lazy as _
from xmltodict import parse from xmltodict import parse
from abonapp.models import Abon, AbonStreet, PassportInfo from abonapp.models import Abon, AbonStreet, PassportInfo
from abonapp.pay_systems import allpay from abonapp.pay_systems import allpay
from group_app.models import Group from group_app.models import Group
from tariff_app.models import Tariff from tariff_app.models import Tariff
from ip_pool.models import NetworkModel
rf = RequestFactory() rf = RequestFactory()
@ -24,7 +28,36 @@ def _make_sign(act: int, pay_account: str, serv_id: str, pay_id):
return md.hexdigest() return md.hexdigest()
class AllPayTestCase(TestCase):
class MyBaseTestCase(metaclass=ABCMeta):
def _client_get_check_login(self, url):
"""
Checks if url is protected from unauthorized access
:param url:
:return: authorized response
"""
r = self.client.get(url)
self.assertRedirects(r, "%s?next=%s" % (getattr(settings, 'LOGIN_URL'), url))
self.client.force_login(self.adminuser)
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
return r
def setUp(self):
grp = Group.objects.create(title='Grp1')
a1 = Abon.objects.create_user(
telephone='+79781234567',
username='abon',
password='passw1'
)
a1.group = grp
a1.save(update_fields=('group',))
my_admin = UserProfile.objects.create_superuser('+79781234567', 'local_superuser', 'ps')
self.adminuser = my_admin
self.abon = a1
self.group = grp
class AllPayTestCase(MyBaseTestCase, TestCase):
pay_url = '/' pay_url = '/'
time_format = '%d.%m.%Y %H:%M' time_format = '%d.%m.%Y %H:%M'
@ -57,18 +90,19 @@ class AllPayTestCase(TestCase):
} }
)) ))
r = r.content.decode('utf-8') r = r.content.decode('utf-8')
self.assertXMLEqual(r, ''.join((
o = ''.join((
"<pay-response>", "<pay-response>",
"<balance>-13.12</balance>", "<balance>-13.12</balance>",
"<name>Test Name</name>", "<name>Test Name</name>",
"<account>pay_account1</account>", "<account>pay_account1</account>",
"<service_id>%s</service_id>" % service_id,
"<service_id>%s</service_id>" % escape(service_id),
"<min_amount>10.0</min_amount>", "<min_amount>10.0</min_amount>",
"<max_amount>5000</max_amount>", "<max_amount>5000</max_amount>",
"<status_code>21</status_code>", "<status_code>21</status_code>",
"<time_stamp>%s</time_stamp>" % current_date,
"<time_stamp>%s</time_stamp>" % escape(current_date),
"</pay-response>" "</pay-response>"
)))
))
self.assertXMLEqual(r, o)
def user_pay_pay(self): def user_pay_pay(self):
print('test_user_pay_pay') print('test_user_pay_pay')
@ -88,10 +122,10 @@ class AllPayTestCase(TestCase):
xml = ''.join(( xml = ''.join((
"<pay-response>", "<pay-response>",
"<pay_id>840ab457-e7d1-4494-8197-9570da035170</pay_id>", "<pay_id>840ab457-e7d1-4494-8197-9570da035170</pay_id>",
"<service_id>%s</service_id>" % service_id,
"<service_id>%s</service_id>" % escape(service_id),
"<amount>18.21</amount>", "<amount>18.21</amount>",
"<status_code>22</status_code>", "<status_code>22</status_code>",
"<time_stamp>%s</time_stamp>" % current_date,
"<time_stamp>%s</time_stamp>" % escape(current_date),
"</pay-response>" "</pay-response>"
)) ))
self.test_pay_time = current_date self.test_pay_time = current_date
@ -113,13 +147,13 @@ class AllPayTestCase(TestCase):
xml = ''.join(( xml = ''.join((
"<pay-response>", "<pay-response>",
"<status_code>11</status_code>", "<status_code>11</status_code>",
"<time_stamp>%s</time_stamp>" % current_date,
"<time_stamp>%s</time_stamp>" % escape(current_date),
"<transaction>", "<transaction>",
"<pay_id>840ab457-e7d1-4494-8197-9570da035170</pay_id>", "<pay_id>840ab457-e7d1-4494-8197-9570da035170</pay_id>",
"<service_id>%s</service_id>" % service_id,
"<service_id>%s</service_id>" % escape(service_id),
"<amount>18.21</amount>", "<amount>18.21</amount>",
"<status>111</status>", "<status>111</status>",
"<time_stamp>%s</time_stamp>" % self.test_pay_time,
"<time_stamp>%s</time_stamp>" % escape(self.test_pay_time),
"</transaction>" "</transaction>"
"</pay-response>" "</pay-response>"
)) ))
@ -160,7 +194,7 @@ class AllPayTestCase(TestCase):
self.assertXMLEqual(r, ''.join(( self.assertXMLEqual(r, ''.join((
"<pay-response>", "<pay-response>",
"<status_code>-40</status_code>", "<status_code>-40</status_code>",
"<time_stamp>%s</time_stamp>" % current_date,
"<time_stamp>%s</time_stamp>" % escape(current_date),
"</pay-response>" "</pay-response>"
))) )))
@ -195,7 +229,7 @@ class AllPayTestCase(TestCase):
xml = ''.join(( xml = ''.join((
"<pay-response>", "<pay-response>",
"<status_code>-10</status_code>", "<status_code>-10</status_code>",
"<time_stamp>%s</time_stamp>" % current_date,
"<time_stamp>%s</time_stamp>" % escape(current_date),
"</pay-response>" "</pay-response>"
)) ))
self.assertXMLEqual(r, xml) self.assertXMLEqual(r, xml)
@ -209,29 +243,26 @@ class AllPayTestCase(TestCase):
self.non_existing_pay() self.non_existing_pay()
class StreetTestCase(TestCase):
class StreetTestCase(MyBaseTestCase, TestCase):
group = None group = None
street = None street = None
def setUp(self): def setUp(self):
grp = Group.objects.create(title='Grp1')
super(StreetTestCase, self).setUp()
grp = self.group
self.street = AbonStreet.objects.create(name='test_street', group=grp) self.street = AbonStreet.objects.create(name='test_street', group=grp)
AbonStreet.objects.create(name='test_street1', group=grp) AbonStreet.objects.create(name='test_street1', group=grp)
AbonStreet.objects.create(name='test_street2', group=grp) AbonStreet.objects.create(name='test_street2', group=grp)
AbonStreet.objects.create(name='test_street3', group=grp) AbonStreet.objects.create(name='test_street3', group=grp)
AbonStreet.objects.create(name='test_street4', group=grp) AbonStreet.objects.create(name='test_street4', group=grp)
AbonStreet.objects.create(name='test_street5', group=grp) AbonStreet.objects.create(name='test_street5', group=grp)
self.group = grp
my_admin = UserProfile.objects.create_superuser('+79781234567', 'local_superuser', 'ps')
# self.client.login(username=my_admin.username, password=my_admin.password)
self.adminuser = my_admin
def test_street_make_cyrillic(self): def test_street_make_cyrillic(self):
print('test_make_cyrillic_street') print('test_make_cyrillic_street')
# title = ''.join(chr(n) for n in range(1072, 1104)) # title = ''.join(chr(n) for n in range(1072, 1104))
cyrrilic = 'абвгдежзийклмнопрстуфхцчшщъыьэюя' cyrrilic = 'абвгдежзийклмнопрстуфхцчшщъыьэюя'
self.client.force_login(self.adminuser)
url = resolve_url('abonapp:street_add', self.group.pk) url = resolve_url('abonapp:street_add', self.group.pk)
self._client_get_check_login(url)
r = self.client.post(url, { r = self.client.post(url, {
'name': cyrrilic, 'name': cyrrilic,
'group': self.group.pk 'group': self.group.pk
@ -243,7 +274,7 @@ class StreetTestCase(TestCase):
print('test_edit_steet') print('test_edit_steet')
url = resolve_url('abonapp:street_edit', self.group.pk) url = resolve_url('abonapp:street_edit', self.group.pk)
streets = AbonStreet.objects.exclude(pk=self.street.pk) streets = AbonStreet.objects.exclude(pk=self.street.pk)
self.client.force_login(self.adminuser)
self._client_get_check_login(url)
r = self.client.post(url, { r = self.client.post(url, {
'sid': tuple(s.id for s in streets), 'sid': tuple(s.id for s in streets),
'sname': tuple('%s_' % s.name for s in streets) 'sname': tuple('%s_' % s.name for s in streets)
@ -264,28 +295,17 @@ class StreetTestCase(TestCase):
self.assertEqual(after_count, 0) self.assertEqual(after_count, 0)
class PassportTestCase(TestCase):
class PassportTestCase(MyBaseTestCase, TestCase):
def setUp(self): def setUp(self):
grp = Group.objects.create(title='Grp1')
a1 = Abon.objects.create_user(
telephone='+79781234567',
username='pay_account1',
password='passw1'
)
a1.group = grp
a1.save(update_fields=('group',))
super(PassportTestCase, self).setUp()
passport_item = PassportInfo.objects.create( passport_item = PassportInfo.objects.create(
series='1243', series='1243',
number='738517', number='738517',
distributor='Distributor', distributor='Distributor',
date_of_acceptance=date(year=2014, month=9, day=14), date_of_acceptance=date(year=2014, month=9, day=14),
abon=a1
abon=self.abon
) )
my_admin = UserProfile.objects.create_superuser('+79781234567', 'local_superuser', 'ps')
self.adminuser = my_admin
self.passport = passport_item self.passport = passport_item
self.abon = a1
self.group = grp
def test_create_update_delete(self): def test_create_update_delete(self):
self.passport_make() self.passport_make()
@ -295,7 +315,7 @@ class PassportTestCase(TestCase):
def passport_make(self): def passport_make(self):
print('passport_make') print('passport_make')
url = resolve_url('abonapp:passport_view', self.group.pk, self.abon.username) url = resolve_url('abonapp:passport_view', self.group.pk, self.abon.username)
self.client.force_login(self.adminuser)
self._client_get_check_login(url)
self.client.post(url, { self.client.post(url, {
'series': '1232', 'series': '1232',
'number': '123456', 'number': '123456',
@ -311,7 +331,6 @@ class PassportTestCase(TestCase):
def passport_change(self): def passport_change(self):
print('passport_change') print('passport_change')
url = resolve_url('abonapp:passport_view', self.group.pk, self.abon.username) url = resolve_url('abonapp:passport_view', self.group.pk, self.abon.username)
self.client.force_login(self.adminuser)
self.client.post(url, { self.client.post(url, {
'series': '9876', 'series': '9876',
'number': '987654', 'number': '987654',
@ -327,36 +346,15 @@ class PassportTestCase(TestCase):
def passport_remove_item_with_user(self): def passport_remove_item_with_user(self):
print('passport_remove_item_with_user') print('passport_remove_item_with_user')
url = resolve_url('abonapp:del_abon', self.group.pk, self.abon.username) url = resolve_url('abonapp:del_abon', self.group.pk, self.abon.username)
self.client.force_login(self.adminuser)
self.client.post(url) self.client.post(url)
passport = PassportInfo.objects.filter(abon=self.abon).first() passport = PassportInfo.objects.filter(abon=self.abon).first()
self.assertIsNone(passport) self.assertIsNone(passport)
class AbonServiceTestCase(TestCase):
def _client_get_check_login(self, url):
"""
Checks if url is protected from unauthorized access
:param url:
:return: authorized response
"""
r = self.client.get(url)
self.assertRedirects(r, "%s?next=%s" % (getattr(settings, 'LOGIN_URL'), url))
self.client.force_login(self.adminuser)
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
return r
class AbonServiceTestCase(MyBaseTestCase, TestCase):
def setUp(self): def setUp(self):
grp = Group.objects.create(title='Grp1')
a1 = Abon.objects.create_user(
telephone='+79781234567',
username='abon',
password='passw1'
)
a1.group = grp
a1.save(update_fields=('group',))
my_admin = UserProfile.objects.create_superuser('+79781234567', 'local_superuser', 'ps')
super().setUp()
tariff1 = Tariff.objects.create( tariff1 = Tariff.objects.create(
title='trf', title='trf',
descr='descr', descr='descr',
@ -365,7 +363,7 @@ class AbonServiceTestCase(TestCase):
amount=1, amount=1,
calc_type='Dp' calc_type='Dp'
) )
tariff1.groups.add(grp)
tariff1.groups.add(self.group)
tariff1.save() tariff1.save()
tariff2 = Tariff.objects.create( tariff2 = Tariff.objects.create(
title='trf2', title='trf2',
@ -375,11 +373,8 @@ class AbonServiceTestCase(TestCase):
amount=2, amount=2,
calc_type='Dp' calc_type='Dp'
) )
tariff2.groups.add(grp)
tariff2.groups.add(self.group)
tariff2.save() tariff2.save()
self.adminuser = my_admin
self.abon = a1
self.group = grp
self.tariff1 = tariff1 self.tariff1 = tariff1
self.tariff2 = tariff2 self.tariff2 = tariff2
@ -423,7 +418,7 @@ class AbonServiceTestCase(TestCase):
updated_abon.save(update_fields=('ballance',)) updated_abon.save(update_fields=('ballance',))
self.client.post(url, data={ self.client.post(url, data={
'tariff': self.tariff1.pk, 'tariff': self.tariff1.pk,
'deadline': self.tariff1.calc_deadline().strftime('%Y-%m-%d')
'deadline': self.tariff1.calc_deadline().strftime('%Y-%m-%d %H:%M:%S')
}) })
updated_abon = Abon.objects.get(username=self.abon.username) updated_abon = Abon.objects.get(username=self.abon.username)
self.assertEqual( self.assertEqual(
@ -433,3 +428,62 @@ class AbonServiceTestCase(TestCase):
self.assertEqual( self.assertEqual(
updated_abon.ballance, 9.0 updated_abon.ballance, 9.0
) )
class ClientLeasesTestCase(MyBaseTestCase, TestCase):
def setUp(self):
super(ClientLeasesTestCase, self).setUp()
netw = NetworkModel.objects.create(
network='192.168.0.0/24',
kind='inet',
description='Descr',
ip_start='192.168.0.3',
ip_end='192.168.0.6'
)
netw.groups.add(self.group.pk)
netw.save()
self.network = netw
def test_add_static_ipv4_lease(self):
print('test_add_static_ipv4_lease')
url = resolve_url('abonapp:lease_add', gid=self.group.pk, uname=self.abon.username)
self._client_get_check_login(url)
# Checks if lease not in allowed range
r = self.client.post(url, data={
'ip_addr': '192.168.0.255',
'is_dynamic': False,
'possible_networks': self.network.pk
})
self.assertFormError(r, form='form', field='ip_addr', errors=_('Ip that you have passed is greater than allowed network range'))
# Not valid ipv4 address
r = self.client.post(url, data={
'ip_addr': '192.168.3.213123',
'is_dynamic': False,
'possible_networks': self.network.pk
})
self.assertFormError(r, form='form', field='ip_addr', errors=_('Enter a valid IPv4 or IPv6 address.'))
# different subnet
r = self.client.post(url, data={
'ip_addr': '192.168.4.2',
'is_dynamic': False,
'possible_networks': self.network.pk
})
self.assertFormError(r, form='form', field='ip_addr', errors=_('Ip that you typed is not in subnet that you have selected'))
# another subnet
netw = NetworkModel.objects.create(
network='192.168.1.0/24',
kind='inet',
description='Descr',
ip_start='192.168.1.3',
ip_end='192.168.1.6'
)
r = self.client.post(url, data={
'ip_addr': '192.168.0.9',
'is_dynamic': False,
'possible_networks': netw.pk
})
self.assertFormError(r, form='form', field='ip_addr', errors=_('Ip that you typed is not in subnet that you have selected'))

2
abonapp/urls.py

@ -55,6 +55,8 @@ urlpatterns = [
url(r'^pay$', views.terminal_pay, name='terminal_pay'), url(r'^pay$', views.terminal_pay, name='terminal_pay'),
url(r'^debtors$', views.DebtorsListView.as_view(), name='debtors'), url(r'^debtors$', views.DebtorsListView.as_view(), name='debtors'),
url(r'^ping$', views.abon_ping, name='ping'), url(r'^ping$', views.abon_ping, name='ping'),
url(r'^contacts/vcards/$', views.vcards, name='vcards'),
# Api's # Api's
url(r'^api/abons$', views.abons), url(r'^api/abons$', views.abons),
url(r'^api/abon_filter$', views.search_abon), url(r'^api/abon_filter$', views.search_abon),

52
abonapp/views.py

@ -1,10 +1,9 @@
from typing import Dict, Optional from typing import Dict, Optional
from datetime import datetime, date, timedelta
from datetime import datetime, date
from django.contrib.gis.shortcuts import render_to_text from django.contrib.gis.shortcuts import render_to_text
from django.core.exceptions import PermissionDenied, ValidationError from django.core.exceptions import PermissionDenied, ValidationError
from django.db import IntegrityError, ProgrammingError, transaction from django.db import IntegrityError, ProgrammingError, transaction
from django.db.models import Count, Q from django.db.models import Count, Q
from django.http.request import QueryDict
from django.shortcuts import render, redirect, get_object_or_404, resolve_url from django.shortcuts import render, redirect, get_object_or_404, resolve_url
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseRedirect from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseRedirect
@ -420,8 +419,7 @@ def pick_tariff(request, gid, uname):
if deadline == '' or deadline is None: if deadline == '' or deadline is None:
abon.pick_tariff(trf, request.user, comment=log_comment) abon.pick_tariff(trf, request.user, comment=log_comment)
else: else:
deadline = datetime.strptime(deadline, '%Y-%m-%d')
deadline += timedelta(hours=23, minutes=59, seconds=59)
deadline = datetime.strptime(deadline, '%Y-%m-%d %H:%M:%S')
abon.pick_tariff(trf, request.user, deadline=deadline, comment=log_comment) abon.pick_tariff(trf, request.user, deadline=deadline, comment=log_comment)
abon.sync_with_nas(created=False) abon.sync_with_nas(created=False)
messages.success(request, _('Tariff has been picked')) messages.success(request, _('Tariff has been picked'))
@ -705,7 +703,49 @@ def abon_ping(request):
} }
@method_decorator((login_required, lib.decorators.only_admins,), name='dispatch')
@login_required
def vcards(r):
abons = models.Abon.objects.exclude(group=None).select_related('group', 'street').only(
'username', 'fio', 'group__title', 'telephone',
'street__name', 'house'
)
additional_tels = models.AdditionalTelephone.objects.select_related('abon', 'abon__group', 'abon__street')
response = HttpResponse(content_type='text/x-vcard')
response['Content-Disposition'] = 'attachment; filename="contacts.vcard"'
tmpl = ("BEGIN:VCARD\r\n"
"VERSION:4.0\r\n"
"FN:%(uname)s. %(group_name)s, %(street)s %(house)s\r\n"
"IMPP:sip:%(abon_telephone)s@dial.lo\r\n"
"END:VCARD\r\n")
def _make_vcard():
for ab in abons.iterator():
tel = ab.telephone
if tel:
yield tmpl % {
'uname': ab.get_full_name(),
'group_name': ab.group.title,
'street': ab.street.name if ab.street else '',
'house': ab.house,
'abon_telephone': tel
}
if not additional_tels.exists():
return
for add_tel in additional_tels.iterator():
abon = add_tel.abon
yield tmpl % {
'uname': "%s (%s)" % (add_tel.owner_name, abon.get_full_name()),
'group_name': abon.group.title,
'abon_telephone': add_tel.telephone,
'street': abon.street.name if abon.street else '',
'house': abon.house
}
response.content = _make_vcard()
return response
@method_decorator((login_required, lib.decorators.only_admins), name='dispatch')
class DialsListView(OrderedFilteredList): class DialsListView(OrderedFilteredList):
context_object_name = 'logs' context_object_name = 'logs'
template_name = 'abonapp/dial_log.html' template_name = 'abonapp/dial_log.html'
@ -1183,7 +1223,7 @@ class DhcpLever(SecureApiView):
def on_dhcp_event(data: Dict) -> Optional[str]: def on_dhcp_event(data: Dict) -> Optional[str]:
""" """
data = { data = {
'client_ip': ip2int('127.0.0.1'),
'client_ip': ip_address('127.0.0.1'),
'client_mac': 'aa:bb:cc:dd:ee:ff', 'client_mac': 'aa:bb:cc:dd:ee:ff',
'switch_mac': 'aa:bb:cc:dd:ee:ff', 'switch_mac': 'aa:bb:cc:dd:ee:ff',
'switch_port': 3, 'switch_port': 3,

2
accounts_app/models.py

@ -95,7 +95,7 @@ class UserProfileManager(MyUserManager):
class UserProfile(BaseAccount): class UserProfile(BaseAccount):
avatar = models.ImageField(_('Avatar'), upload_to=os.path.join('user', 'avatar'), null=True, default=None) avatar = models.ImageField(_('Avatar'), upload_to=os.path.join('user', 'avatar'), null=True, default=None)
email = models.EmailField(default='admin@example.ru')
email = models.EmailField(default='')
responsibility_groups = models.ManyToManyField(Group, blank=True, verbose_name=_('Responsibility groups')) responsibility_groups = models.ManyToManyField(Group, blank=True, verbose_name=_('Responsibility groups'))
objects = UserProfileManager() objects = UserProfileManager()

4
agent/core.py

@ -97,8 +97,10 @@ class BaseTransmitter(ABC):
:return: Tuple of 2 lists that contain list to add users and list to remove users :return: Tuple of 2 lists that contain list to add users and list to remove users
""" """
users_struct_list = (ab.build_agent_struct() for ab in users_from_db if ab.is_access()) users_struct_list = (ab.build_agent_struct() for ab in users_from_db if ab.is_access())
users_struct_set = set([ab for ab in users_struct_list if ab is not None and ab.tariff is not None])
users_struct_set = set(ab for ab in users_struct_list if ab is not None and ab.tariff is not None)
users_from_nas = set(self.read_users()) users_from_nas = set(self.read_users())
if len(users_from_nas) < 1:
print('WARNING: Not have users from NAS')
list_for_del = (users_struct_set ^ users_from_nas) - users_struct_set list_for_del = (users_struct_set ^ users_from_nas) - users_struct_set
list_for_add = users_struct_set - users_from_nas list_for_add = users_struct_set - users_from_nas
return list_for_add, list_for_del return list_for_add, list_for_del

68
agent/mod_mikrotik.py

@ -3,13 +3,14 @@ import socket
import binascii import binascii
from abc import ABCMeta from abc import ABCMeta
from hashlib import md5 from hashlib import md5
from ipaddress import ip_network
from ipaddress import ip_network, _BaseAddress
from typing import Iterable, Optional, Tuple, Generator, Dict from typing import Iterable, Optional, Tuple, Generator, Dict
from django.conf import settings
from djing.lib.decorators import LazyInitMetaclass from djing.lib.decorators import LazyInitMetaclass
from .structs import TariffStruct, AbonStruct, IpStruct, VectorAbon, VectorTariff from .structs import TariffStruct, AbonStruct, IpStruct, VectorAbon, VectorTariff
from . import settings as local_settings from . import settings as local_settings
from django.conf import settings
from djing import ping from djing import ping
from agent.core import BaseTransmitter, NasNetworkError, NasFailedResult from agent.core import BaseTransmitter, NasNetworkError, NasFailedResult
@ -231,22 +232,29 @@ class MikrotikTransmitter(BaseTransmitter, ApiRos, metaclass=type('_ABC_Lazy_mcs
res = float(re.sub(r'[a-zA-Z]', '', text_speed)) / 1000 ** 2 res = float(re.sub(r'[a-zA-Z]', '', text_speed)) / 1000 ** 2
return res return res
dat = info.get('!re')
speeds = dat.get('=max-limit').split('/')
speed_out, speed_in = info['=max-limit'].split('/')
t = TariffStruct( t = TariffStruct(
speed_in=parse_speed(speeds[1]),
speed_out=parse_speed(speeds[0])
speed_in=parse_speed(speed_in),
speed_out=parse_speed(speed_out)
) )
try: try:
a = AbonStruct(
uid=int(dat['=name'][3:]),
# FIXME: тут в разных микротиках или =target-addresses или =target
ips=(int(ip_network(ip).network_address) for ip in dat['=target'].split(',')),
tariff=t,
is_access=False if dat['=disabled'] == 'false' else True
)
a.queue_id = dat['=.id']
return a
target = info.get('=target')
if target is None:
target = info.get('=target-addresses')
name = info.get('=name')
disabled = info.get('=disabled')
if disabled is not None:
disabled = True if disabled == 'true' else False
if target is not None and name is not None:
target_ip, target_net = target.split('/')
a = AbonStruct(
uid=int(name[3:]),
ip=target_ip,
tariff=t,
is_active=disabled or False
)
a.queue_id = info.get('=.id')
return a
except ValueError: except ValueError:
pass pass
@ -348,8 +356,8 @@ class MikrotikTransmitter(BaseTransmitter, ApiRos, metaclass=type('_ABC_Lazy_mcs
'=numbers=%s' % ','.join(ip_firewall_ids) '=numbers=%s' % ','.join(ip_firewall_ids)
)) ))
def find_ip(self, ip: IpStruct, list_name: str):
if not isinstance(ip, IpStruct):
def find_ip(self, ip, list_name: str):
if not issubclass(ip.__class__, _BaseAddress):
raise TypeError raise TypeError
r = self._exec_cmd(( r = self._exec_cmd((
'/ip/firewall/address-list/print', 'where', '/ip/firewall/address-list/print', 'where',
@ -407,7 +415,7 @@ class MikrotikTransmitter(BaseTransmitter, ApiRos, metaclass=type('_ABC_Lazy_mcs
# queue is instance of AbonStruct # queue is instance of AbonStruct
queue = self.find_queue('uid%d' % user.uid) queue = self.find_queue('uid%d' % user.uid)
for ip in user.ips: for ip in user.ips:
if not isinstance(ip, IpStruct):
if not issubclass(ip.__class__, _BaseAddress):
raise TypeError raise TypeError
nas_ip = self.find_ip(ip, LIST_USERS_ALLOWED) nas_ip = self.find_ip(ip, LIST_USERS_ALLOWED)
if user.is_access: if user.is_access:
@ -469,14 +477,28 @@ class MikrotikTransmitter(BaseTransmitter, ApiRos, metaclass=type('_ABC_Lazy_mcs
pass pass
def read_users(self) -> VectorAbon: def read_users(self) -> VectorAbon:
class ip_mkid_struct(object):
__slots__ = ('ip', 'mkid')
def __init__(self, ip, mkid):
self.ip = ip
self.mkid = mkid
def __eq__(self, other):
if isinstance(other, ip_mkid_struct):
return self.ip == other.ip
return self.ip == str(other)
def __hash__(self):
return hash(self.ip)
# shapes is ShapeItem # shapes is ShapeItem
allowed_ips = tuple(self.read_ips_iter(LIST_USERS_ALLOWED))
queues = tuple(q for q in self.read_queue_iter() if set(q.ips).issubset(allowed_ips))
# TODO: Make clean old ip addresses in other place
#ips_from_queues = set((q.ip, q) for q in queues)
all_ips = set(ip_mkid_struct(ip, mkid) for ip, mkid in self.read_ips_iter(LIST_USERS_ALLOWED))
queues = (q for q in self.read_queue_iter() if str(q.ip) in all_ips)
# ips_from_queues = set(str(q.ip) for q in queues)
# delete ip addresses that are in firewall/address-list and there are no corresponding in queues # delete ip addresses that are in firewall/address-list and there are no corresponding in queues
#diff = tuple(allowed_ips - ips_from_queues)
#diff = tuple(all_ips - ips_from_queues)
#if len(diff) > 0: #if len(diff) > 0:
# self.remove_ip_range(diff) # self.remove_ip_range(diff)
return queues return queues

8
agent/structs.py

@ -44,9 +44,9 @@ class TariffStruct(BaseStruct):
self.speedIn = speed_in or 0 self.speedIn = speed_in or 0
self.speedOut = speed_out or 0 self.speedOut = speed_out or 0
# Да, если все значения нулевые
# Yes, if all variables is zeroed
def is_empty(self): def is_empty(self):
return self.tid == 0 and self.speedIn == 0.001 and self.speedOut == 0.001
return self.tid == 0 and self.speedIn == 0 and self.speedOut == 0
def __eq__(self, other): def __eq__(self, other):
# не сравниваем id, т.к. тарифы с одинаковыми скоростями для NAS одинаковы # не сравниваем id, т.к. тарифы с одинаковыми скоростями для NAS одинаковы
@ -62,7 +62,7 @@ class TariffStruct(BaseStruct):
return hash(str(self.speedIn) + str(self.speedOut)) return hash(str(self.speedIn) + str(self.speedOut))
# Абонент из базы
# Abon from database
class AbonStruct(BaseStruct): class AbonStruct(BaseStruct):
__slots__ = ('uid', 'ips', 'tariff', 'is_access', 'queue_id') __slots__ = ('uid', 'ips', 'tariff', 'is_access', 'queue_id')
@ -90,7 +90,7 @@ class AbonStruct(BaseStruct):
return hash(hash(self.ips) + hash(self.tariff)) if self.tariff is not None else 0 return hash(hash(self.ips) + hash(self.tariff)) if self.tariff is not None else 0
# Правило шейпинга в фаере, или ещё можно сказать услуга абонента на NAS
# Shape rule from NAS(Network Access Server)
class ShapeItem(BaseStruct): class ShapeItem(BaseStruct):
__slots__ = ('abon', 'sid') __slots__ = ('abon', 'sid')

16
agent/utils.py

@ -1,16 +0,0 @@
import socket
import struct
def ip2int(addr):
try:
return struct.unpack("!I", socket.inet_aton(addr))[0]
except:
return 0
def int2ip(addr):
try:
return socket.inet_ntoa(struct.pack("!I", addr))
except:
return ''

19
chatbot/email_bot.py

@ -0,0 +1,19 @@
from smtplib import SMTPException
from django.core.mail import send_mail
from django.conf import settings
from chatbot.models import ChatException, MessageQueue
def send_notify(msg_text, account, tag='none'):
try:
MessageQueue.objects.push(msg=msg_text, user=account, tag=tag)
target_email = account.email
send_mail(
subject=getattr(settings, 'COMPANY_NAME', 'Djing notify'),
message=msg_text,
from_email=getattr(settings, 'DEFAULT_FROM_EMAIL'),
recipient_list=(target_email,)
)
except SMTPException as e:
raise ChatException('SMTPException: %s' % e)

5
chatbot/send_func.py

@ -0,0 +1,5 @@
# send via email
from .email_bot import send_notify
# for Telegram
# from chatbot.telebot import send_notify

9
devapp/dev_types.py

@ -499,12 +499,13 @@ class ZteOnuDevice(OnuDevice):
raise DeviceConfigurationError('For ZTE configuration needed "telnet" section in extra_data') raise DeviceConfigurationError('For ZTE configuration needed "telnet" section in extra_data')
login = telnet.get('login') login = telnet.get('login')
password = telnet.get('password') password = telnet.get('password')
if login is None or password is None:
raise DeviceConfigurationError('For ZTE configuration needed login and'
' password for telnet access in extra_data')
prompt = telnet.get('prompt')
if login is None or password is None or prompt is None:
raise DeviceConfigurationError('For ZTE configuration needed login, password and'
' prompt for telnet access in extra_data')
stack_num, rack_num, fiber_num, new_onu_port_num = register_onu_ZTE_F660( stack_num, rack_num, fiber_num, new_onu_port_num = register_onu_ZTE_F660(
olt_ip=ip, onu_sn=sn, login_passwd=(login.encode(), password.encode()), olt_ip=ip, onu_sn=sn, login_passwd=(login.encode(), password.encode()),
onu_mac=mac
onu_mac=mac, prompt_title=prompt.encode(), vlan_id=132
) )
bin_snmp_fiber_number = "10000{0:08b}{1:08b}00000000".format(rack_num, fiber_num) bin_snmp_fiber_number = "10000{0:08b}{1:08b}00000000".format(rack_num, fiber_num)
snmp_fiber_num = int(bin_snmp_fiber_number, base=2) snmp_fiber_num = int(bin_snmp_fiber_number, base=2)

13
devapp/views.py

@ -1,4 +1,5 @@
import re import re
from ipaddress import ip_address
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib.gis.shortcuts import render_to_text from django.contrib.gis.shortcuts import render_to_text
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
@ -22,7 +23,7 @@ from accounts_app.models import UserProfile
from django.conf import settings from django.conf import settings
from guardian.decorators import permission_required_or_403 as permission_required from guardian.decorators import permission_required_or_403 as permission_required
from guardian.shortcuts import get_objects_for_user from guardian.shortcuts import get_objects_for_user
from chatbot.telebot import send_notify
from chatbot.send_func import send_notify
from chatbot.models import ChatException from chatbot.models import ChatException
from jsonview.decorators import json_view from jsonview.decorators import json_view
from djing import global_base_views, MAC_ADDR_REGEX, ping, get_object_or_None from djing import global_base_views, MAC_ADDR_REGEX, ping, get_object_or_None
@ -519,9 +520,13 @@ def search_dev(request):
if word is None or word == '': if word is None or word == '':
results = [{'id': 0, 'text': ''}] results = [{'id': 0, 'text': ''}]
else: else:
results = Device.objects.filter(
Q(comment__icontains=word) | Q(ip_address=word)
).only('pk', 'ip_address', 'comment')[:16]
qs = Q(comment__icontains=word)
try:
ip = ip_address(word)
qs |= Q(ip_address=str(ip))
except ValueError:
pass
results = Device.objects.filter(qs).only('pk', 'ip_address', 'comment')[:16]
results = [{ results = [{
'id': device.pk, 'id': device.pk,
'text': "%s: %s" % (device.ip_address or '', device.comment) 'text': "%s: %s" % (device.ip_address or '', device.comment)

2
dhcp_lever.py

@ -16,7 +16,7 @@ def die(text):
''' '''
obj = { obj = {
'client_ip': ip2int('127.0.0.1'),
'client_ip': ip_address('127.0.0.1'),
'client_mac': 'aa:bb:cc:dd:ee:ff', 'client_mac': 'aa:bb:cc:dd:ee:ff',
'switch_mac': 'aa:bb:cc:dd:ee:ff', 'switch_mac': 'aa:bb:cc:dd:ee:ff',
'switch_port': 3, 'switch_port': 3,

12
djing/fields.py

@ -1,11 +1,11 @@
# #
# I got it on https://github.com/django-macaddress/django-macaddress # I got it on https://github.com/django-macaddress/django-macaddress
# #
from ipaddress import ip_address
from netaddr import EUI, AddrFormatError
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import models from django.db import models
from netaddr import EUI, AddrFormatError
from djing.lib import ip2int, int2ip
from .formfields import MACAddressField as MACAddressFormField from .formfields import MACAddressField as MACAddressFormField
from . import default_dialect from . import default_dialect
import warnings import warnings
@ -121,7 +121,8 @@ class MyGenericIPAddressField(models.GenericIPAddressField):
def get_prep_value(self, value): def get_prep_value(self, value):
# strIp to Int # strIp to Int
value = super(MyGenericIPAddressField, self).get_prep_value(value) value = super(MyGenericIPAddressField, self).get_prep_value(value)
return ip2int(value)
if value:
return int(ip_address(value))
def to_python(self, value): def to_python(self, value):
return value return value
@ -131,7 +132,8 @@ class MyGenericIPAddressField(models.GenericIPAddressField):
@staticmethod @staticmethod
def from_db_value(value, expression, connection, context): def from_db_value(value, expression, connection, context):
return int2ip(value) if value != 0 else None
if value:
return str(ip_address(value))
def int_ip(self): def int_ip(self):
return ip2int(self)
return int(ip_address(self))

23
djing/lib/tln/tln.py

@ -40,12 +40,11 @@ ONU_SN_REGEX = b'^ZTEG[A-F\d]{8}$'
class TelnetApi(Telnet): class TelnetApi(Telnet):
def __init__(self, *args, **kwargs):
def __init__(self, prompt_string: bytes, *args, **kwargs):
timeout = kwargs.get('timeout') timeout = kwargs.get('timeout')
if timeout: if timeout:
self._timeout = timeout self._timeout = timeout
self._prompt_string = b'ZTE-C320-PKP#'
self.config_level = []
self._prompt_string = prompt_string or b'ZTE#'
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
def write(self, buffer: bytes) -> None: def write(self, buffer: bytes) -> None:
@ -106,8 +105,10 @@ def parse_onu_name(onu_name: bytes, name_regexp=re.compile(b'[/:_]')) -> Dict[st
class OltZTERegister(TelnetApi): class OltZTERegister(TelnetApi):
def __init__(self, screen_size: Tuple[int, int], *args, **kwargs):
super().__init__(*args, **kwargs)
def __init__(self, screen_size: Tuple[int, int], prompt_title: bytes, *args, **kwargs):
super().__init__(prompt_string=prompt_title, *args, **kwargs)
self.prompt_title = prompt_title
self.set_prompt_string(b'%s#' % prompt_title)
self.resize_screen(*screen_size) self.resize_screen(*screen_size)
def enter(self, username: bytes, passw: bytes) -> None: def enter(self, username: bytes, passw: bytes) -> None:
@ -153,7 +154,7 @@ class OltZTERegister(TelnetApi):
return last_onu return last_onu
def enter_to_config_mode(self) -> bool: def enter_to_config_mode(self) -> bool:
prompt = b'ZTE-C320-PKP(config)#'
prompt = b'%s(config)#' % self.prompt_title
self.set_prompt_string(prompt) self.set_prompt_string(prompt)
res = tuple(self.command_to(b'config terminal')) res = tuple(self.command_to(b'config terminal'))
if res[1].startswith(b'Enter configuration commands'): if res[1].startswith(b'Enter configuration commands'):
@ -162,7 +163,7 @@ class OltZTERegister(TelnetApi):
return False return False
def go_to_olt_interface(self, stack_num: int, rack_num: int, fiber_num: int) -> Tuple: def go_to_olt_interface(self, stack_num: int, rack_num: int, fiber_num: int) -> Tuple:
self.set_prompt_string(b'ZTE-C320-PKP(config-if)#')
self.set_prompt_string(b'%s(config-if)#' % self.prompt_title)
return tuple(self.command_to(b'interface gpon-olt_%d/%d/%d' % ( return tuple(self.command_to(b'interface gpon-olt_%d/%d/%d' % (
stack_num, stack_num,
rack_num, rack_num,
@ -170,7 +171,7 @@ class OltZTERegister(TelnetApi):
))) )))
def go_to_onu_interface(self, stack_num: int, rack_num: int, fiber_num: int, onu_port_num: int) -> Tuple: def go_to_onu_interface(self, stack_num: int, rack_num: int, fiber_num: int, onu_port_num: int) -> Tuple:
self.set_prompt_string(b'ZTE-C320-PKP(config-if)#')
self.set_prompt_string(b'%s(config-if)#' % self.prompt_title)
return tuple(self.command_to(b'interface gpon-onu_%d/%d/%d:%d' % ( return tuple(self.command_to(b'interface gpon-onu_%d/%d/%d:%d' % (
stack_num, stack_num,
rack_num, rack_num,
@ -204,7 +205,7 @@ class OltZTERegister(TelnetApi):
@process_lock @process_lock
def register_onu_ZTE_F660(olt_ip: str, onu_sn: bytes, login_passwd: Tuple[bytes, bytes], onu_mac: bytes) -> Tuple:
def register_onu_ZTE_F660(olt_ip: str, onu_sn: bytes, login_passwd: Tuple[bytes, bytes], onu_mac: bytes, prompt_title: bytes, vlan_id: int) -> Tuple:
onu_type = b'ZTE-F660' onu_type = b'ZTE-F660'
line_profile = b'ZTE-F660-LINE' line_profile = b'ZTE-F660-LINE'
remote_profile = b'ZTE-F660-ROUTER' remote_profile = b'ZTE-F660-ROUTER'
@ -215,7 +216,7 @@ def register_onu_ZTE_F660(olt_ip: str, onu_sn: bytes, login_passwd: Tuple[bytes,
if not re.match(ONU_SN_REGEX, onu_sn): if not re.match(ONU_SN_REGEX, onu_sn):
raise ValidationError raise ValidationError
tn = OltZTERegister(host=olt_ip, timeout=2, screen_size=(120, 128))
tn = OltZTERegister(host=olt_ip, timeout=2, screen_size=(120, 128), prompt_title=prompt_title)
tn.enter(*login_passwd) tn.enter(*login_passwd)
unregistered_onu = tn.get_unregistered_onu(onu_sn) unregistered_onu = tn.get_unregistered_onu(onu_sn)
@ -254,7 +255,7 @@ def register_onu_ZTE_F660(olt_ip: str, onu_sn: bytes, login_passwd: Tuple[bytes,
r = tn.go_to_onu_interface(stack_num, rack_num, fiber_num, new_onu_port_num) r = tn.go_to_onu_interface(stack_num, rack_num, fiber_num, new_onu_port_num)
print(r) print(r)
tn.apply_conf_to_onu(onu_mac, 145)
tn.apply_conf_to_onu(onu_mac, vlan_id)
sleep(1) sleep(1)
return stack_num, rack_num, fiber_num, new_onu_port_num return stack_num, rack_num, fiber_num, new_onu_port_num

7
djing/local_settings.py.template

@ -62,3 +62,10 @@ API_AUTH_SUBNET = '127.0.0.0/8'
# Company name # Company name
COMPANY_NAME = 'Your company name' COMPANY_NAME = 'Your company name'
# Email config
EMAIL_HOST_USER = 'YOUR-EMAIL@mailserver.com'
EMAIL_HOST = 'smtp.mailserver.com'
EMAIL_PORT = 587
EMAIL_HOST_PASSWORD = 'password'
EMAIL_USE_TLS = True

14
djing/settings.py

@ -131,11 +131,11 @@ SESSION_COOKIE_HTTPONLY = True
# Internationalization # Internationalization
# https://docs.djangoproject.com/en/1.11/topics/i18n/ # https://docs.djangoproject.com/en/1.11/topics/i18n/
LANGUAGE_CODE = 'ru'
LANGUAGE_CODE = 'en'
LANGUAGES = ( LANGUAGES = (
('ru', _('Russian')), ('ru', _('Russian')),
#('en', _('English'))
('en', _('English'))
) )
PROJECT_PATH = os.path.dirname(os.path.abspath(__file__)) PROJECT_PATH = os.path.dirname(os.path.abspath(__file__))
@ -214,6 +214,16 @@ BOOTSTRAP3 = {
'horizontal_field_class': 'col-md-9', 'horizontal_field_class': 'col-md-9',
} }
# Email config
EMAIL_HOST_USER = local_settings.EMAIL_HOST_USER
EMAIL_HOST = local_settings.EMAIL_HOST
EMAIL_PORT = local_settings.EMAIL_PORT
EMAIL_HOST_PASSWORD = local_settings.EMAIL_HOST_PASSWORD
EMAIL_USE_TLS = getattr(local_settings, 'EMAIL_USE_TLS', True)
SERVER_EMAIL = EMAIL_HOST_USER
# Inactive ip lease time in seconds. # Inactive ip lease time in seconds.
# If lease time more than time of create, and lease is inactive # If lease time more than time of create, and lease is inactive
# then delete it. Used in ip_pool app. # then delete it. Used in ip_pool app.

2
docs/dev.md

@ -282,7 +282,7 @@ def add_tariff_range(self, tariff_list):
Для того чтоб оправить важное сообщение работнику через все возможные настроенные системы(смс, телеграм, браузер) мы можем Для того чтоб оправить важное сообщение работнику через все возможные настроенные системы(смс, телеграм, браузер) мы можем
воспользоваться одной процедурой из модуля **chatbot**. воспользоваться одной процедурой из модуля **chatbot**.
```python ```python
from chatbot.telebot import send_notify
from chatbot.send_func import send_notify
send_notify(msg_text='Text message',account=employee_profile, tag='apptag') send_notify(msg_text='Text message',account=employee_profile, tag='apptag')
``` ```

28
docs/dhcp.md

@ -0,0 +1,28 @@
## ISC-DHCP Сервер, взаимодействие с биллингом.
Вобщих чертах взаимодействие происходит с помощью скрипта **dhcp_lever.py**
в корне проекта. Запущенный DHCP сервер, при возникновении событий запускает
этот сценарий , а тот говорит биллингу подробнее что произошло.
При событии *expiry* или *release* биллингу нужно освободить ip, а при *commit*
нужно назначить динамическую аренду ip для учётной записи абонента в биллинге.
Сам скрипт не выполняет все эти действия, он просто отправляет полученные от dhcp
сервера параметры на url адрес для обработки dhcp. View распологается в **abonapp.views.DhcpLever**.
### Выделение аренды ip
Как происходит выделение аренды ip, от события в dhcp сервере и до появления интернета у
абонента.
Когда в dhcp сервере происходит событие *commit* то из **abonapp.views.DhcpLever** вызывается
функция **agent.commands.dhcp.dhcp_commit**, с помощью DHCP OPTION.82 получаем mac адрес управляемого
свича и порт через который пришёл запрос. Каждое такое устройство должно быть зарегистрировано в биллинге.
Далее ищем в базе абонента, или абонентов к которому привязано устройство с переданным mac адресом.
Проверяем может-ли данный тип устройства содержать несколько подключённых абонентов(напрмер PON ONU, в основном,
содержит одного абонента). Проверка происходит по свойству **is_use_device_port** из менеджера устройства,
которое открыто для кастомизации, подробнее в [Менеджер устройства](./docs/dev.md).
А далее, если может быть несколько абонентов, то фильтруем вывод ещё по порту свича.
Получется что на управляемом свиче мы авторизуем абонентов при помощи dhcp option.82 по маку свича и порту абонента.
Если наше устройство PON ONU(ONT) то авторизуем только по mac адресу оптического юнита(onu).
После добавления абоненту аренды динамического ip, он(абонент) синхронизуется с nas сервером и открывается доступ
к интернету в соответствии с тарифом абонента.

20
docs/extra_func.md

@ -0,0 +1,20 @@
## Дополнительный фунционал
В процессе реализации проекта понадобился функционал, который отсутствует в базовой поставке **Django**.
Его совсем не много, но без внимания оставить нельзя.
Все вспомогательные модули можно найти в пакете **djing.lib**.
### tln
Это модуль работы по *telnet*
### messaging
Этот модуль помогает работать с форматами СМС сообщений.
### init
Содержит всякие мелкие примочки, код прост и с комментариями, зайдите посмотрите.
## auth_decorators
Бэкенд авторизации
## decorators
Дополнительные декораторы.

5
docs/install.md

@ -12,10 +12,10 @@
Затем установим зависимости Затем установим зависимости
``` ```
# dnf -y install python3 python3-devel python3-pip python3-pillow mariadb mariadb-devel uwsgi nginx uwsgi-plugin-python3 net-snmp net-snmp-libs net-snmp-utils net-snmp-devel net-snmp-python git redhat-rpm-config
# dnf -y install python3 python3-devel python3-pip python3-pillow mariadb mariadb-devel uwsgi nginx uwsgi-plugin-python3 net-snmp net-snmp-libs net-snmp-utils net-snmp-devel net-snmp-python git redhat-rpm-config curl-devel
``` ```
Лучше чтоб версия python по умолчанию была третья:
Необходимо чтоб версия python по умолчанию была третья:
``` ```
# ln -sf python3 /usr/bin/python # ln -sf python3 /usr/bin/python
``` ```
@ -27,6 +27,7 @@
# cd /var/www # cd /var/www
# pip3 install --upgrade pip # pip3 install --upgrade pip
# git clone https://github.com/nerosketch/djing.git # git clone https://github.com/nerosketch/djing.git
# export PYCURL_SSL_LIBRARY=openssl
# pip3 install -r djing/requirements.txt # pip3 install -r djing/requirements.txt
``` ```

2
docs/map.md

@ -24,4 +24,4 @@
### Другое ### Другое
Статусы устройств на точках с одним устройством обновляются автоматически 2 раза в минуту. Статусы устройств на точках с одним устройством обновляются автоматически 2 раза в минуту.
Это означает что когда вы изучаете карту и какое-либо устройство пропало из сети то вы Это означает что когда вы изучаете карту и какое-либо устройство пропало из сети то вы
увидите это сразу без перезагрузки карты.
увидите это в течение 2х минут без перезагрузки карты.

3
docs/netflow.md

@ -27,5 +27,6 @@ rm -rf djing_flow_git
вы найдёте этот самый файл дампа трафика. И тут уже можно посмотреть как работает утилита **djing_flow**: вы найдёте этот самый файл дампа трафика. И тут уже можно посмотреть как работает утилита **djing_flow**:
> \$ ./djing_flow < /tmp/djing_flow_dump.tmp > \$ ./djing_flow < /tmp/djing_flow_dump.tmp
На выходе вы получите запрос для mysql. Можно перенаправить его по конвееру в mysql, реализацию вы можете увидеть в файле
На выходе вы получите запрос для mysql. Можно перенаправить его по конвееру в mysql, рабочий пример
перенаправления этогй утилиты вы можете увидеть в файле
*agent/netflow/netflow_handler.sh*. *agent/netflow/netflow_handler.sh*.

14
docs/user_page.md

@ -0,0 +1,14 @@
## Особенности страницы абонента.
Находится она в разделе **Абоненты** внутри группы. На этой странице вы увидите несколько логических блоков,
из которых самые важные, пожалуй, 2. Первый это **Изменение абонента** а второй **Выберите устройство**.
На первом блоке можно редактировать базовую информацию абонента. Если снять галку *Активен* то абонент перестанет
получать услуги даже при подключённой услуге.
Блок с устройством содержит то самое устройство, к которому подключён абонент. Если это устройство не будет назначено
то биллинг не сможет авторизовать абонента по dhcp option.82. Галочка **Динамические настройки по dhcp** означает что
учётная запись абонента сможет получать динамический ip. Это означает что если галка не будет выставлена, то сколько бы
запросов не приходило с этого устройства абонент не изменить свой ip, это полезно когда абонент работает со статическим
ip.
Вверху есть вкладки. с соответствующим названию функционалом. Например на вкладке **Тарифы** вы можете назначить
абоненту услугу или добавить периодический платёж, который абонент увидит в своём личном кабинете.

6
docs/views.md

@ -22,8 +22,8 @@ class PaysListView(ListView):
``` ```
Тогда в шаблоне с bootstrap вы можете увидеть примерно такую пагинацию которую
вы конечно же можете изменить на свою.
Тогда в шаблоне с bootstrap вы можете подключить шаблон пагинации *templates/pagination.html* и
увидеть примерно такую пагинацию которую вы конечно же можете изменить на свою.
![paginator](./img/pagination.png). ![paginator](./img/pagination.png).
@ -53,4 +53,4 @@ class PaysListView(ListView, OrderingMixin):
pass pass
``` ```
Примесь *OrderingMixin* добавляет в контекст переменные *order_by* и *dir* для использования в шалоне.
Примесь *OrderingMixin* добавляет в контекст переменные *order_by* и *dir* для использования в шаблоне.

1
group_app/views.py

@ -69,7 +69,6 @@ class DeleteGroupView(DeleteView):
group_with_subscribers = models.Group.objects.annotate( group_with_subscribers = models.Group.objects.annotate(
subscribers_count=Count('abon') subscribers_count=Count('abon')
).filter(subscribers_count__gt=0, pk=group_id).first() ).filter(subscribers_count__gt=0, pk=group_id).first()
print('group_with_subscribers:', group_with_subscribers)
if group_with_subscribers is not None: if group_with_subscribers is not None:
messages.error(request, _('Group is contain subscribers. Remove them before delete group')) messages.error(request, _('Group is contain subscribers. Remove them before delete group'))
return HttpResponseRedirect(self.success_url) return HttpResponseRedirect(self.success_url)

2
msg_app/models.py

@ -1,7 +1,7 @@
from django.db import models from django.db import models
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from accounts_app.models import UserProfile from accounts_app.models import UserProfile
from chatbot.telebot import send_notify
from chatbot.send_func import send_notify
from chatbot.models import ChatException from chatbot.models import ChatException

6
periodic.py

@ -40,9 +40,9 @@ def main():
try: try:
tm = Transmitter() tm = Transmitter()
users = Abon.objects.filter(is_active=True).exclude(current_tariff=None) users = Abon.objects.filter(is_active=True).exclude(current_tariff=None)
tm.sync_nas(users)
except NasNetworkError as e:
print('NetworkTrouble:', e)
tm.sync_nas(users.iterator())
except NasNetworkError as er:
print('NetworkTrouble:', er)
# manage periodic pays # manage periodic pays
ppays = PeriodicPayForId.objects.filter(next_pay__lt=now) \ ppays = PeriodicPayForId.objects.filter(next_pay__lt=now) \

14
tariff_app/custom_tariffs.py

@ -67,11 +67,23 @@ class TariffCp(TariffDp):
return long_long_time return long_long_time
# Daily service
class TariffDaily(TariffDp):
description = _('IS Daily service')
def calc_deadline(self):
nw = timezone.now()
# next day in the same time
one_day = timedelta(days=1)
return nw + one_day
# Первый - всегда по умолчанию # Первый - всегда по умолчанию
TARIFF_CHOICES = ( TARIFF_CHOICES = (
('Df', TariffDefault), ('Df', TariffDefault),
('Dp', TariffDp), ('Dp', TariffDp),
('Cp', TariffCp)
('Cp', TariffCp),
('Dl', TariffDaily)
) )

3
tariff_app/locale/ru/LC_MESSAGES/django.po

@ -212,3 +212,6 @@ msgstr "Периодический платёж изменён"
msgid "Are you sure you want to delete tariff?" msgid "Are you sure you want to delete tariff?"
msgstr "Вы уверены что хотите удалить тариф?" msgstr "Вы уверены что хотите удалить тариф?"
msgid "IS Daily service"
msgstr "Услуга на сутки"

4
tariff_app/views.py

@ -40,9 +40,9 @@ def edit_tarif(request, tarif_id=0):
if request.method == 'POST': if request.method == 'POST':
frm = forms.TariffForm(request.POST, instance=tarif) frm = forms.TariffForm(request.POST, instance=tarif)
if frm.is_valid(): if frm.is_valid():
frm.save()
new_service = frm.save()
messages.success(request, _('Service has been saved')) messages.success(request, _('Service has been saved'))
return redirect('tarifs:edit', tarif_id=tarif_id)
return redirect('tarifs:edit', tarif_id=new_service.pk)
else: else:
messages.warning(request, _('Some fields were filled incorrect, please try again')) messages.warning(request, _('Some fields were filled incorrect, please try again'))
else: else:

2
taskapp/handle.py

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from chatbot.telebot import send_notify
from chatbot.send_func import send_notify
from chatbot.models import ChatException from chatbot.models import ChatException
from djing.lib import MultipleException from djing.lib import MultipleException

19
taskapp/models.py

@ -6,7 +6,7 @@ from django.conf import settings
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from abonapp.models import Abon from abonapp.models import Abon
# from .handle import handle as task_handle
from .handle import handle as task_handle
TASK_PRIORITIES = ( TASK_PRIORITIES = (
('A', _('Higher')), ('A', _('Higher')),
@ -100,15 +100,14 @@ class Task(models.Model):
self.save(update_fields=('state',)) self.save(update_fields=('state',))
def send_notification(self): def send_notification(self):
pass
#if self.abon:
# group = self.abon.group
#else:
# group = ''
#task_handle(
# self, self.author,
# self.recipients.all(), group
#)
if self.abon:
group = self.abon.group
else:
group = ''
task_handle(
self, self.author,
self.recipients.all(), group
)
def get_attachment_fname(self): def get_attachment_fname(self):
return os.path.basename(self.attachment.name) return os.path.basename(self.attachment.name)

Loading…
Cancel
Save