Browse Source
Merge branch 'devel' into fin
Merge branch 'devel' into fin
Conflicts: abonapp/forms.py abonapp/migrations/0002_auto_20180808_1448.py abonapp/models.py abonapp/templates/abonapp/group_list.html abonapp/views.py clientsideapp/views.py djing/local_settings.py.example djing/settings.py djing/urls.py locale/ru/LC_MESSAGES/django.po periodic.py Changes to be committed: modified: abonapp/forms.py modified: abonapp/locale/ru/LC_MESSAGES/django.po new file: abonapp/migrations/0001_squashed_0008_auto_20181115_1206.py modified: abonapp/migrations/0002_auto_20180808_1448.py modified: abonapp/models.py new file: abonapp/tasks.py modified: abonapp/templates/abonapp/debtors.html modified: abonapp/templates/abonapp/editAbon.html modified: abonapp/templates/abonapp/group_list.html modified: abonapp/templates/abonapp/invoiceForPayment.html modified: abonapp/templates/abonapp/modal_phonebook.html modified: abonapp/templates/abonapp/payHistory.html modified: abonapp/templates/abonapp/peoples.html modified: abonapp/templates/abonapp/service.html modified: abonapp/views.py modified: accounts_app/forms.py modified: accounts_app/locale/ru/LC_MESSAGES/django.po new file: accounts_app/migrations/0004_userprofile_flags.py modified: accounts_app/models.py modified: accounts_app/templates/accounts/acc_list.html modified: accounts_app/templates/accounts/ext.htm modified: accounts_app/templates/accounts/index.html modified: accounts_app/templates/accounts/settings/ext.htm renamed: accounts_app/templates/accounts/userprofile_form.html -> accounts_app/templates/accounts/settings/userprofile_form.html modified: accounts_app/urls.py modified: accounts_app/views.py new file: agent/netflow/djing_flow.tar.gz modified: agent/netflow/netflow_handler.py modified: agent/netflow/start_netflow.sh deleted: chatbot/admin.py deleted: chatbot/apps.py deleted: chatbot/locale/ru/LC_MESSAGES/django.po deleted: chatbot/migrations/0001_initial.py deleted: chatbot/migrations/0002_auto_20180808_1236.py deleted: chatbot/models.py deleted: chatbot/telebot.py deleted: chatbot/views.py modified: clientsideapp/views.py modified: devapp/base_intr.py modified: devapp/dev_types.py modified: devapp/forms.py modified: devapp/locale/ru/LC_MESSAGES/django.po new file: devapp/migrations/0001_squashed_0005_device_ip_address_change.py modified: devapp/models.py new file: devapp/onu_config/__init__.py new file: devapp/onu_config/base.py new file: devapp/onu_config/f601.py new file: devapp/onu_config/f660.py modified: devapp/templates/devapp/custom_dev_page/generic_switch.html modified: devapp/templates/devapp/custom_dev_page/onu.html modified: devapp/templates/devapp/custom_dev_page/onu_for_zte.html modified: devapp/templates/devapp/group_list.html modified: devapp/urls.py modified: devapp/views.py modified: dialing_app/templates/index.html modified: dialing_app/templatetags/telephone_filters.py modified: djing/fields.py modified: djing/lib/decorators.py modified: djing/lib/mixins.py deleted: djing/lib/tln/__init__.py deleted: djing/lib/tln/tln.py modified: djing/local_settings.py.example modified: djing/settings.py modified: djing/tasks.py modified: djing/urls.py modified: djing/views.py modified: docs/extra_func.md modified: group_app/views.py modified: gw_app/nas_managers/core.py modified: gw_app/nas_managers/mod_mikrotik.py modified: gw_app/nas_managers/structs.py new file: ip_pool/migrations/0001_squashed_0004_auto_20190305_1243.py new file: ip_pool/migrations/0004_auto_20190305_1243.py modified: ip_pool/models.py modified: locale/ru/LC_MESSAGES/django.po new file: messenger/__init__.py new file: messenger/admin.py new file: messenger/apps.py new file: messenger/forms.py new file: messenger/locale/ru/LC_MESSAGES/django.po new file: messenger/migrations/0001_initial.py renamed: chatbot/__init__.py -> messenger/migrations/__init__.py new file: messenger/models.py new file: messenger/tasks.py new file: messenger/templates/messenger/add_messenger.html new file: messenger/templates/messenger/messenger_list.html new file: messenger/templates/messenger/vibermessenger_form.html new file: messenger/tests.py new file: messenger/urls.py new file: messenger/views.py modified: msg_app/forms.py modified: msg_app/models.py modified: msg_app/templates/msg_app/chat.html modified: msg_app/urls.py modified: msg_app/views.py modified: periodic.py modified: requirements.txt modified: searchapp/templates/searchapp/index.html modified: static/bad_ie.html modified: static/css/all.min.css deleted: static/css/bootstrap-datetimepicker.min.css new file: static/css/chartist.min.css deleted: static/img/gmarkers/dev.png deleted: static/img/gmarkers/dev_bug.png deleted: static/img/gmarkers/dev_ok.png deleted: static/img/gmarkers/flag_black.png deleted: static/img/gmarkers/relay_rack.png modified: static/js/all.min.js deleted: static/js/bootstrap-datetimepicker.min.js new file: static/js/chartist.min.js deleted: static/js/cidr.js modified: static/js/my.js deleted: systemd_units/djing_telebot.service modified: systemd_units/do_backup.sh modified: taskapp/forms.py modified: taskapp/handle.py modified: taskapp/locale/ru/LC_MESSAGES/django.po modified: taskapp/models.py modified: taskapp/templates/taskapp/details.html modified: taskapp/templates/taskapp/footer_btns.html modified: taskapp/urls.py modified: taskapp/views.py deleted: telebot.py modified: templates/all_base.html modified: templates/base.html renamed: chatbot/migrations/__init__.py -> traf_stat/__init__.py new file: traf_stat/admin.py new file: traf_stat/apps.py new file: traf_stat/fields.py new file: traf_stat/migrations/0001_initial.py new file: traf_stat/migrations/__init__.py new file: traf_stat/models.py new file: traf_stat/templates/statistics/index.html new file: traf_stat/tests.py new file: traf_stat/urls.py new file: traf_stat/views.pydevel
136 changed files with 2831 additions and 1529 deletions
-
13abonapp/forms.py
-
3abonapp/locale/ru/LC_MESSAGES/django.po
-
356abonapp/migrations/0001_squashed_0008_auto_20181115_1206.py
-
7abonapp/migrations/0002_auto_20180808_1448.py
-
46abonapp/models.py
-
47abonapp/tasks.py
-
14abonapp/templates/abonapp/debtors.html
-
2abonapp/templates/abonapp/editAbon.html
-
7abonapp/templates/abonapp/group_list.html
-
2abonapp/templates/abonapp/invoiceForPayment.html
-
2abonapp/templates/abonapp/modal_phonebook.html
-
4abonapp/templates/abonapp/payHistory.html
-
43abonapp/templates/abonapp/peoples.html
-
2abonapp/templates/abonapp/service.html
-
50abonapp/views.py
-
5accounts_app/forms.py
-
180accounts_app/locale/ru/LC_MESSAGES/django.po
-
19accounts_app/migrations/0004_userprofile_flags.py
-
9accounts_app/models.py
-
11accounts_app/templates/accounts/acc_list.html
-
9accounts_app/templates/accounts/ext.htm
-
24accounts_app/templates/accounts/index.html
-
18accounts_app/templates/accounts/settings/ext.htm
-
2accounts_app/templates/accounts/settings/userprofile_form.html
-
21accounts_app/urls.py
-
114accounts_app/views.py
-
BINagent/netflow/djing_flow.tar.gz
-
7agent/netflow/netflow_handler.py
-
2agent/netflow/start_netflow.sh
-
6chatbot/admin.py
-
5chatbot/apps.py
-
112chatbot/locale/ru/LC_MESSAGES/django.po
-
64chatbot/migrations/0001_initial.py
-
27chatbot/migrations/0002_auto_20180808_1236.py
-
73chatbot/models.py
-
149chatbot/telebot.py
-
3chatbot/views.py
-
3clientsideapp/views.py
-
14devapp/base_intr.py
-
174devapp/dev_types.py
-
4devapp/forms.py
-
49devapp/locale/ru/LC_MESSAGES/django.po
-
120devapp/migrations/0001_squashed_0005_device_ip_address_change.py
-
6devapp/models.py
-
6devapp/onu_config/__init__.py
-
86devapp/onu_config/base.py
-
165devapp/onu_config/f601.py
-
141devapp/onu_config/f660.py
-
18devapp/templates/devapp/custom_dev_page/generic_switch.html
-
5devapp/templates/devapp/custom_dev_page/onu.html
-
15devapp/templates/devapp/custom_dev_page/onu_for_zte.html
-
2devapp/templates/devapp/group_list.html
-
52devapp/urls.py
-
58devapp/views.py
-
2dialing_app/templates/index.html
-
2dialing_app/templatetags/telephone_filters.py
-
1djing/fields.py
-
22djing/lib/decorators.py
-
8djing/lib/mixins.py
-
4djing/lib/tln/__init__.py
-
274djing/lib/tln/tln.py
-
3djing/local_settings.py.example
-
10djing/settings.py
-
2djing/tasks.py
-
3djing/urls.py
-
2djing/views.py
-
3docs/extra_func.md
-
7group_app/views.py
-
5gw_app/nas_managers/core.py
-
16gw_app/nas_managers/mod_mikrotik.py
-
4gw_app/nas_managers/structs.py
-
36ip_pool/migrations/0001_squashed_0004_auto_20190305_1243.py
-
15ip_pool/migrations/0004_auto_20190305_1243.py
-
2ip_pool/models.py
-
3locale/ru/LC_MESSAGES/django.po
-
1messenger/__init__.py
-
7messenger/admin.py
-
5messenger/apps.py
-
28messenger/forms.py
-
190messenger/locale/ru/LC_MESSAGES/django.po
-
88messenger/migrations/0001_initial.py
-
0messenger/migrations/__init__.py
-
128messenger/models.py
-
56messenger/tasks.py
-
15messenger/templates/messenger/add_messenger.html
-
57messenger/templates/messenger/messenger_list.html
-
49messenger/templates/messenger/vibermessenger_form.html
-
3messenger/tests.py
-
17messenger/urls.py
-
161messenger/views.py
-
2msg_app/forms.py
-
25msg_app/models.py
-
4msg_app/templates/msg_app/chat.html
-
3msg_app/urls.py
-
22msg_app/views.py
-
11periodic.py
-
21requirements.txt
-
4searchapp/templates/searchapp/index.html
-
15static/bad_ie.html
-
4static/css/all.min.css
@ -0,0 +1,356 @@ |
|||||
|
# Generated by Django 2.1.1 on 2019-03-05 20:00 |
||||
|
|
||||
|
import bitfield.models |
||||
|
import django.core.validators |
||||
|
import django.db.models.deletion |
||||
|
from django.conf import settings |
||||
|
from django.db import migrations, models |
||||
|
|
||||
|
|
||||
|
class Migration(migrations.Migration): |
||||
|
replaces = [('abonapp', '0001_initial'), ('abonapp', '0002_auto_20180808_1448'), ('abonapp', '0003_abon_nas'), |
||||
|
('abonapp', '0004_auto_20180918_1734'), ('abonapp', '0005_current_tariff'), |
||||
|
('abonapp', '0006_change_ip'), ('abonapp', '0007_auto_20181101_1545'), |
||||
|
('abonapp', '0008_auto_20181115_1206')] |
||||
|
|
||||
|
initial = True |
||||
|
|
||||
|
dependencies = [ |
||||
|
('tariff_app', '0003_auto_20181115_1206'), |
||||
|
('gw_app', '0001_initial'), |
||||
|
('tariff_app', '0001_initial'), |
||||
|
('ip_pool', '0001_initial'), |
||||
|
('gw_app', '0002_auto_20181101_1545'), |
||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL), |
||||
|
('group_app', '0001_initial'), |
||||
|
('accounts_app', '0001_initial'), |
||||
|
('devapp', '0001_initial'), |
||||
|
] |
||||
|
|
||||
|
operations = [ |
||||
|
migrations.CreateModel( |
||||
|
name='AbonTariff', |
||||
|
fields=[ |
||||
|
('id', models.AutoField( |
||||
|
auto_created=True, primary_key=True, |
||||
|
serialize=False, verbose_name='ID' |
||||
|
)), |
||||
|
('tariff', models.ForeignKey( |
||||
|
on_delete=django.db.models.deletion.CASCADE, |
||||
|
related_name='linkto_tariff', |
||||
|
to='tariff_app.Tariff' |
||||
|
)), |
||||
|
('time_start', models.DateTimeField( |
||||
|
blank=True, default=None, null=True |
||||
|
)), |
||||
|
('deadline', models.DateTimeField( |
||||
|
blank=True, default=None, null=True |
||||
|
)), |
||||
|
], |
||||
|
options={ |
||||
|
'ordering': ('time_start',), |
||||
|
'permissions': (('can_complete_service', 'finish service perm'),), |
||||
|
'verbose_name': 'Abon service', |
||||
|
'verbose_name_plural': 'Abon services', |
||||
|
'db_table': 'abonent_tariff' |
||||
|
}, |
||||
|
), |
||||
|
migrations.CreateModel( |
||||
|
name='AbonStreet', |
||||
|
fields=[ |
||||
|
('id', models.AutoField( |
||||
|
auto_created=True, primary_key=True, |
||||
|
serialize=False, verbose_name='ID')), |
||||
|
('name', models.CharField(max_length=64)), |
||||
|
('group', models.ForeignKey( |
||||
|
on_delete=django.db.models.deletion.CASCADE, |
||||
|
to='group_app.Group' |
||||
|
)), |
||||
|
], |
||||
|
options={ |
||||
|
'verbose_name': 'Street', |
||||
|
'verbose_name_plural': 'Streets', |
||||
|
'db_table': 'abon_street', |
||||
|
'ordering': ('name',), |
||||
|
}, |
||||
|
), |
||||
|
migrations.CreateModel( |
||||
|
name='Abon', |
||||
|
fields=[ |
||||
|
('baseaccount_ptr', models.OneToOneField( |
||||
|
auto_created=True, on_delete=django.db.models.deletion.CASCADE, |
||||
|
parent_link=True, primary_key=True, serialize=False, |
||||
|
to='accounts_app.BaseAccount' |
||||
|
)), |
||||
|
('current_tariff', models.OneToOneField( |
||||
|
blank=True, default=None, null=True, |
||||
|
on_delete=django.db.models.deletion.SET_NULL, |
||||
|
to='abonapp.AbonTariff' |
||||
|
)), |
||||
|
('group', models.ForeignKey( |
||||
|
blank=True, null=True, |
||||
|
on_delete=django.db.models.deletion.SET_NULL, |
||||
|
to='group_app.Group', verbose_name='User group' |
||||
|
)), |
||||
|
('ballance', models.FloatField(default=0.0)), |
||||
|
('ip_address', models.GenericIPAddressField( |
||||
|
blank=True, null=True, |
||||
|
verbose_name='Ip address' |
||||
|
)), |
||||
|
('description', models.TextField( |
||||
|
blank=True, null=True, verbose_name='Comment' |
||||
|
)), |
||||
|
('street', models.ForeignKey( |
||||
|
blank=True, null=True, |
||||
|
on_delete=django.db.models.deletion.SET_NULL, |
||||
|
to='abonapp.AbonStreet', verbose_name='Street' |
||||
|
)), |
||||
|
('house', models.CharField( |
||||
|
blank=True, max_length=12, |
||||
|
null=True, verbose_name='House' |
||||
|
)), |
||||
|
('device', models.ForeignKey( |
||||
|
blank=True, null=True, |
||||
|
on_delete=django.db.models.deletion.SET_NULL, |
||||
|
to='devapp.Device' |
||||
|
)), |
||||
|
('dev_port', models.ForeignKey( |
||||
|
blank=True, null=True, |
||||
|
on_delete=django.db.models.deletion.SET_NULL, |
||||
|
to='devapp.Port' |
||||
|
)), |
||||
|
('is_dynamic_ip', models.BooleanField( |
||||
|
default=False, verbose_name='Is dynamic ip' |
||||
|
)), |
||||
|
('nas', models.ForeignKey( |
||||
|
blank=True, default=None, null=True, |
||||
|
on_delete=django.db.models.deletion.SET_NULL, |
||||
|
to='gw_app.NASModel', verbose_name='Network access server' |
||||
|
)), |
||||
|
('autoconnect_service', models.BooleanField( |
||||
|
default=False, verbose_name='Automatically connect next service' |
||||
|
)), |
||||
|
('last_connected_tariff', models.ForeignKey( |
||||
|
blank=True, default=None, null=True, |
||||
|
on_delete=django.db.models.deletion.SET_NULL, |
||||
|
to='tariff_app.Tariff', |
||||
|
verbose_name='Last connected service' |
||||
|
)), |
||||
|
('markers', bitfield.models.BitField( |
||||
|
(('icon_donkey', 'Donkey'), ('icon_fire', 'Fire'), |
||||
|
('icon_ok', 'Ok'), ('icon_king', 'King'), |
||||
|
('icon_tv', 'TV'), ('icon_smile', 'Smile'), |
||||
|
('icon_dollar', 'Dollar'), ('icon_service', 'Service'), |
||||
|
('icon_mrk', 'Marker')), default=0 |
||||
|
)), |
||||
|
], |
||||
|
options={ |
||||
|
'ordering': ('fio',), |
||||
|
'permissions': ( |
||||
|
('can_buy_tariff', 'Buy service perm'), |
||||
|
('can_add_ballance', 'fill account'), |
||||
|
('can_ping', 'Can ping')), |
||||
|
'verbose_name': 'Abon', |
||||
|
'verbose_name_plural': 'Abons', |
||||
|
'db_table': 'abonent', |
||||
|
'unique_together': {('ip_address', 'nas')} |
||||
|
}, |
||||
|
bases=('accounts_app.baseaccount',), |
||||
|
), |
||||
|
migrations.CreateModel( |
||||
|
name='AbonLog', |
||||
|
fields=[ |
||||
|
('id', models.AutoField( |
||||
|
auto_created=True, primary_key=True, |
||||
|
serialize=False, verbose_name='ID' |
||||
|
)), |
||||
|
('abon', models.ForeignKey( |
||||
|
on_delete=django.db.models.deletion.CASCADE, |
||||
|
to='abonapp.Abon')), |
||||
|
('amount', models.FloatField(default=0.0)), |
||||
|
('author', models.ForeignKey( |
||||
|
blank=True, null=True, |
||||
|
on_delete=django.db.models.deletion.SET_NULL, |
||||
|
related_name='+', to=settings.AUTH_USER_MODEL |
||||
|
)), |
||||
|
('comment', models.CharField(max_length=128)), |
||||
|
('date', models.DateTimeField(auto_now_add=True)), |
||||
|
], |
||||
|
options={ |
||||
|
'db_table': 'abonent_log', |
||||
|
'ordering': ('-date',) |
||||
|
}, |
||||
|
), |
||||
|
migrations.CreateModel( |
||||
|
name='AdditionalTelephone', |
||||
|
fields=[ |
||||
|
('abon', models.ForeignKey( |
||||
|
on_delete=django.db.models.deletion.CASCADE, |
||||
|
related_name='additional_telephones', |
||||
|
to='abonapp.Abon' |
||||
|
)), |
||||
|
('id', models.AutoField( |
||||
|
auto_created=True, primary_key=True, |
||||
|
serialize=False, verbose_name='ID' |
||||
|
)), |
||||
|
('telephone', models.CharField( |
||||
|
max_length=16, validators=[ |
||||
|
django.core.validators.RegexValidator('^(\\+[7,8,9,3]\\d{10,11})?$') |
||||
|
], |
||||
|
verbose_name='Telephone') |
||||
|
), |
||||
|
('owner_name', models.CharField(max_length=127)), |
||||
|
], |
||||
|
options={ |
||||
|
'verbose_name': 'Additional telephone', |
||||
|
'verbose_name_plural': 'Additional telephones', |
||||
|
'db_table': 'additional_telephones', |
||||
|
'ordering': ('owner_name',), |
||||
|
}, |
||||
|
), |
||||
|
migrations.CreateModel( |
||||
|
name='AllPayLog', |
||||
|
fields=[ |
||||
|
('pay_id', models.CharField(max_length=64, primary_key=True, serialize=False)), |
||||
|
('date_action', models.DateTimeField(auto_now_add=True)), |
||||
|
('summ', models.FloatField(default=0.0)), |
||||
|
('pay_system_name', models.CharField(max_length=16)), |
||||
|
], |
||||
|
options={ |
||||
|
'db_table': 'all_pay_log', |
||||
|
'ordering': ('-date_action',), |
||||
|
}, |
||||
|
), |
||||
|
migrations.CreateModel( |
||||
|
name='AllTimePayLog', |
||||
|
fields=[ |
||||
|
('abon', |
||||
|
models.ForeignKey( |
||||
|
blank=True, default=None, null=True, |
||||
|
on_delete=django.db.models.deletion.SET_DEFAULT, |
||||
|
to='abonapp.Abon' |
||||
|
)), |
||||
|
('pay_id', models.CharField( |
||||
|
max_length=36, primary_key=True, |
||||
|
serialize=False, unique=True |
||||
|
)), |
||||
|
('date_add', models.DateTimeField(auto_now_add=True)), |
||||
|
('summ', models.FloatField(default=0.0)), |
||||
|
('trade_point', models.CharField( |
||||
|
blank=True, default=None, max_length=20, |
||||
|
null=True, verbose_name='Trade point' |
||||
|
)), |
||||
|
('receipt_num', models.BigIntegerField(default=0, verbose_name='Receipt number')), |
||||
|
], |
||||
|
options={ |
||||
|
'db_table': 'all_time_pay_log', |
||||
|
'ordering': ('-date_add',), |
||||
|
}, |
||||
|
), |
||||
|
migrations.CreateModel( |
||||
|
name='InvoiceForPayment', |
||||
|
fields=[ |
||||
|
('id', models.AutoField( |
||||
|
auto_created=True, primary_key=True, |
||||
|
serialize=False, verbose_name='ID' |
||||
|
)), |
||||
|
('abon', models.ForeignKey( |
||||
|
on_delete=django.db.models.deletion.CASCADE, |
||||
|
to='abonapp.Abon' |
||||
|
)), |
||||
|
('status', models.BooleanField(default=False)), |
||||
|
('amount', models.FloatField(default=0.0)), |
||||
|
('comment', models.CharField(max_length=128)), |
||||
|
('date_create', models.DateTimeField(auto_now_add=True)), |
||||
|
('date_pay', models.DateTimeField(blank=True, null=True)), |
||||
|
('author', models.ForeignKey( |
||||
|
blank=True, null=True, |
||||
|
on_delete=django.db.models.deletion.SET_NULL, |
||||
|
related_name='+', to=settings.AUTH_USER_MODEL |
||||
|
)) |
||||
|
], |
||||
|
options={ |
||||
|
'verbose_name': 'Debt', |
||||
|
'verbose_name_plural': 'Debts', |
||||
|
'db_table': 'abonent_inv_pay', |
||||
|
'ordering': ('date_create',) |
||||
|
}, |
||||
|
), |
||||
|
migrations.CreateModel( |
||||
|
name='PassportInfo', |
||||
|
fields=[ |
||||
|
('id', models.AutoField( |
||||
|
auto_created=True, primary_key=True, |
||||
|
serialize=False, verbose_name='ID' |
||||
|
)), |
||||
|
('series', models.CharField( |
||||
|
max_length=4, validators=[django.core.validators.integer_validator], |
||||
|
verbose_name='Pasport serial' |
||||
|
)), |
||||
|
('number', models.CharField( |
||||
|
max_length=6, validators=[django.core.validators.integer_validator], |
||||
|
verbose_name='Pasport number' |
||||
|
)), |
||||
|
('distributor', models.CharField( |
||||
|
max_length=64, verbose_name='Distributor' |
||||
|
)), |
||||
|
('date_of_acceptance', models.DateField( |
||||
|
verbose_name='Date of acceptance' |
||||
|
)), |
||||
|
('abon', models.OneToOneField( |
||||
|
blank=True, null=True, |
||||
|
on_delete=django.db.models.deletion.CASCADE, |
||||
|
to='abonapp.Abon' |
||||
|
)) |
||||
|
], |
||||
|
options={ |
||||
|
'ordering': ('series',), |
||||
|
'verbose_name': 'Passport Info', |
||||
|
'verbose_name_plural': 'Passport Info', |
||||
|
'db_table': 'passport_info' |
||||
|
}, |
||||
|
), |
||||
|
migrations.CreateModel( |
||||
|
name='PeriodicPayForId', |
||||
|
fields=[ |
||||
|
('id', models.AutoField( |
||||
|
auto_created=True, primary_key=True, |
||||
|
serialize=False, verbose_name='ID' |
||||
|
)), |
||||
|
('periodic_pay', models.ForeignKey( |
||||
|
on_delete=django.db.models.deletion.CASCADE, |
||||
|
to='tariff_app.PeriodicPay', |
||||
|
verbose_name='Periodic pay' |
||||
|
)), |
||||
|
('last_pay', models.DateTimeField( |
||||
|
blank=True, null=True, verbose_name='Last pay time' |
||||
|
)), |
||||
|
('next_pay', models.DateTimeField( |
||||
|
verbose_name='Next time to pay' |
||||
|
)), |
||||
|
('account', models.ForeignKey( |
||||
|
on_delete=django.db.models.deletion.CASCADE, |
||||
|
to='abonapp.Abon', |
||||
|
verbose_name='Account' |
||||
|
)) |
||||
|
], |
||||
|
options={ |
||||
|
'db_table': 'periodic_pay_for_id', |
||||
|
'ordering': ('last_pay',) |
||||
|
}, |
||||
|
), |
||||
|
migrations.CreateModel( |
||||
|
name='AbonRawPassword', |
||||
|
fields=[ |
||||
|
('account', models.OneToOneField( |
||||
|
on_delete=django.db.models.deletion.CASCADE, |
||||
|
primary_key=True, serialize=False, |
||||
|
to='abonapp.Abon' |
||||
|
)), |
||||
|
('passw_text', models.CharField(max_length=64)), |
||||
|
], |
||||
|
options={ |
||||
|
'db_table': 'abon_raw_password', |
||||
|
}, |
||||
|
), |
||||
|
] |
||||
@ -0,0 +1,47 @@ |
|||||
|
from celery import shared_task |
||||
|
|
||||
|
from abonapp.models import Abon |
||||
|
from djing.lib import LogicError |
||||
|
from gw_app.models import NASModel |
||||
|
from gw_app.nas_managers import NasFailedResult, NasNetworkError, SubnetQueue |
||||
|
|
||||
|
|
||||
|
@shared_task |
||||
|
def customer_nas_command(customer_uid: int, command: str): |
||||
|
if command not in ('add', 'sync'): |
||||
|
return 'Command required' |
||||
|
try: |
||||
|
cust = Abon.objects.get(pk=customer_uid) |
||||
|
print(cust, command) |
||||
|
if command == 'sync': |
||||
|
r = cust.nas_sync_self() |
||||
|
if isinstance(r, Exception): |
||||
|
return 'ABONAPP SYNC ERROR: %s' % r |
||||
|
elif command == 'add': |
||||
|
cust.nas_add_self() |
||||
|
else: |
||||
|
return 'ABONAPP SYNC ERROR: Unknown command "%s"' % command |
||||
|
except Abon.DoesNotExist: |
||||
|
pass |
||||
|
except (LogicError, NasFailedResult, NasNetworkError, ConnectionResetError) as e: |
||||
|
return 'ABONAPP ERROR: %s' % e |
||||
|
|
||||
|
|
||||
|
@shared_task |
||||
|
def customer_nas_remove(customer_uid: int, ip_addr: str, speed: tuple, is_access: bool, nas_pk: int): |
||||
|
try: |
||||
|
if not isinstance(ip_addr, (str, int)): |
||||
|
ip_addr = str(ip_addr) |
||||
|
sq = SubnetQueue( |
||||
|
name="uid%d" % customer_uid, |
||||
|
network=ip_addr, |
||||
|
max_limit=speed, |
||||
|
is_access=is_access |
||||
|
) |
||||
|
nas = NASModel.objects.get(pk=nas_pk) |
||||
|
mngr = nas.get_nas_manager() |
||||
|
mngr.remove_user(sq) |
||||
|
except (ValueError, NasFailedResult, NasNetworkError, LogicError) as e: |
||||
|
return 'ABONAPP ERROR: %s' % e |
||||
|
except NASModel.DoesNotExist: |
||||
|
return 'NASModel.DoesNotExist id=%d' % nas_pk |
||||
@ -0,0 +1,19 @@ |
|||||
|
# Generated by Django 2.1.3 on 2018-12-24 15:52 |
||||
|
|
||||
|
import bitfield.models |
||||
|
from django.db import migrations |
||||
|
|
||||
|
|
||||
|
class Migration(migrations.Migration): |
||||
|
|
||||
|
dependencies = [ |
||||
|
('accounts_app', '0003_new_user_profile_log'), |
||||
|
] |
||||
|
|
||||
|
operations = [ |
||||
|
migrations.AddField( |
||||
|
model_name='userprofile', |
||||
|
name='flags', |
||||
|
field=bitfield.models.BitField((('notify_task', 'Notification about tasks'), ('notify_msg', 'Notification about messages'), ('notify_mon', 'Notification from monitoring')), default=0, verbose_name='Flags'), |
||||
|
), |
||||
|
] |
||||
@ -1,6 +1,6 @@ |
|||||
#!/usr/bin/env bash |
#!/usr/bin/env bash |
||||
|
|
||||
PATH=/usr/local/sbin:/usr/local/bin:/usr/bin |
|
||||
|
PATH=/bin:/usr/local/sbin:/usr/local/bin:/usr/bin |
||||
|
|
||||
mkdir -p /tmp/djing_flow |
mkdir -p /tmp/djing_flow |
||||
|
|
||||
|
|||||
@ -1,6 +0,0 @@ |
|||||
from django.contrib import admin |
|
||||
from . import models |
|
||||
|
|
||||
admin.site.register(models.MessageHistory) |
|
||||
admin.site.register(models.TelegramBot) |
|
||||
admin.site.register(models.MessageQueue) |
|
||||
@ -1,5 +0,0 @@ |
|||||
from django.apps import AppConfig |
|
||||
|
|
||||
|
|
||||
class ChatbotConfig(AppConfig): |
|
||||
name = 'chatbot' |
|
||||
@ -1,112 +0,0 @@ |
|||||
# 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: 2018-08-09 14:57+0300\n" |
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" |
|
||||
"Last-Translator: Dmitry Novikov <nerosketch@gmail.com>\n" |
|
||||
"Language-Team: LANGUAGE <LL@li.org>\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" |
|
||||
|
|
||||
#: models.py:13 |
|
||||
msgid "Employee" |
|
||||
msgstr "Сотрудник" |
|
||||
|
|
||||
#: models.py:14 |
|
||||
msgid "Telegram chat id" |
|
||||
msgstr "Номер чата из telegram" |
|
||||
|
|
||||
#: models.py:21 |
|
||||
msgid "Telegram bot" |
|
||||
msgstr "Telegram бот" |
|
||||
|
|
||||
#: models.py:22 |
|
||||
msgid "Telegram bots" |
|
||||
msgstr "Telegram боты" |
|
||||
|
|
||||
#: models.py:36 |
|
||||
msgid "Message history" |
|
||||
msgstr "История собщений" |
|
||||
|
|
||||
#: models.py:37 |
|
||||
msgid "Message histories" |
|
||||
msgstr "Истории собщений" |
|
||||
|
|
||||
#: models.py:54 |
|
||||
msgid "Target employee" |
|
||||
msgstr "Сотрудник" |
|
||||
|
|
||||
#: models.py:55 |
|
||||
msgid "Message" |
|
||||
msgstr "Сообщение" |
|
||||
|
|
||||
#: models.py:60 |
|
||||
msgid "Status of message" |
|
||||
msgstr "Статус сообщения" |
|
||||
|
|
||||
#: models.py:62 |
|
||||
msgid "App tag" |
|
||||
msgstr "Тэг приложения" |
|
||||
|
|
||||
#: models.py:71 models.py:72 |
|
||||
msgid "Message queue" |
|
||||
msgstr "Очередь оповещений" |
|
||||
|
|
||||
#: telebot.py:64 |
|
||||
msgid "Let's get acquainted, what is your name? Write your login from billing." |
|
||||
msgstr "Давай знакомиться, как тебя зовут? Напиши свой логин из биллинга." |
|
||||
|
|
||||
#: telebot.py:85 |
|
||||
msgid "I do not know the answer to this yet." |
|
||||
msgstr "Я пока не знаю ответа на это" |
|
||||
|
|
||||
#: telebot.py:106 |
|
||||
msgid "" |
|
||||
"You are not found in the database, check that it correctly pointed out its " |
|
||||
"LOGIN. Try again" |
|
||||
msgstr "" |
|
||||
"Ты не найден в базе, проверь что правильно указал именно свой ЛОГИН. " |
|
||||
"Попробуй ещё" |
|
||||
|
|
||||
#: telebot.py:110 |
|
||||
#, python-format |
|
||||
msgid "" |
|
||||
"Yes, it's nice to meet %(username)s, I will notify you about events in " |
|
||||
"billing. Successful work ;)" |
|
||||
msgstr "" |
|
||||
"Да, приятно познакомиться %(username)s, я буду оповещать тебя о событиях в " |
|
||||
"биллинге. Удачной работы ;)" |
|
||||
|
|
||||
#: telebot.py:122 |
|
||||
msgid "Let's ping, write ip. It will be necessary to wait 10 seconds" |
|
||||
msgstr "Давай пинганём, напиши ip. Нужно будет подождать 10 сек" |
|
||||
|
|
||||
#: telebot.py:129 |
|
||||
msgid "It's not like ip address, try again" |
|
||||
msgstr "Это не похоже на ip адрес, попробуй ещё" |
|
||||
|
|
||||
#: telebot.py:132 |
|
||||
#, python-format |
|
||||
msgid "You're '%s', right?" |
|
||||
msgstr "Ты ведь %s ?" |
|
||||
|
|
||||
#: telebot.py:140 |
|
||||
msgid "Telegram bot token not found" |
|
||||
msgstr "Токен для бота Telegram не найден" |
|
||||
|
|
||||
#: telebot.py:145 |
|
||||
#, python-format |
|
||||
msgid "Recipient '%s' does not subscribed on notifications" |
|
||||
msgstr "%s не подписан на оповещения" |
|
||||
@ -1,64 +0,0 @@ |
|||||
# -*- coding: utf-8 -*- |
|
||||
# Generated by Django 1.11 on 2018-02-26 00:20 |
|
||||
from __future__ import unicode_literals |
|
||||
|
|
||||
from django.conf import settings |
|
||||
from django.db import migrations, models |
|
||||
import django.db.models.deletion |
|
||||
|
|
||||
|
|
||||
class Migration(migrations.Migration): |
|
||||
initial = True |
|
||||
|
|
||||
dependencies = [ |
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL), |
|
||||
] |
|
||||
|
|
||||
operations = [ |
|
||||
migrations.CreateModel( |
|
||||
name='MessageHistory', |
|
||||
fields=[ |
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
|
||||
('message', models.CharField(max_length=255)), |
|
||||
('date_sent', models.DateTimeField(auto_now_add=True)), |
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), |
|
||||
], |
|
||||
options={ |
|
||||
'verbose_name': 'Message history', |
|
||||
'verbose_name_plural': 'Message histories', |
|
||||
'db_table': 'chat_message_history', |
|
||||
}, |
|
||||
), |
|
||||
migrations.CreateModel( |
|
||||
name='MessageQueue', |
|
||||
fields=[ |
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
|
||||
('message', models.CharField(max_length=255, verbose_name='Message')), |
|
||||
('status', models.CharField(choices=[('n', 'New'), ('r', 'Read')], default='n', max_length=1, |
|
||||
verbose_name='Status of message')), |
|
||||
('tag', models.CharField(default='none', max_length=6, verbose_name='App tag')), |
|
||||
('target_employee', |
|
||||
models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, |
|
||||
verbose_name='Target employee')), |
|
||||
], |
|
||||
options={ |
|
||||
'verbose_name': 'Message queue', |
|
||||
'verbose_name_plural': 'Message queue', |
|
||||
'db_table': 'chat_message_queue', |
|
||||
}, |
|
||||
), |
|
||||
migrations.CreateModel( |
|
||||
name='TelegramBot', |
|
||||
fields=[ |
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
|
||||
('chat_id', models.PositiveIntegerField(default=0, verbose_name='Telegram chat id')), |
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, |
|
||||
verbose_name='Employee')), |
|
||||
], |
|
||||
options={ |
|
||||
'verbose_name': 'Telegram bot', |
|
||||
'verbose_name_plural': 'Telegram bots', |
|
||||
'db_table': 'chat_telegram_bot', |
|
||||
}, |
|
||||
), |
|
||||
] |
|
||||
@ -1,27 +0,0 @@ |
|||||
# -*- coding: utf-8 -*- |
|
||||
# Generated by Django 1.11 on 2018-08-08 12:36 |
|
||||
from __future__ import unicode_literals |
|
||||
|
|
||||
from django.db import migrations |
|
||||
|
|
||||
|
|
||||
class Migration(migrations.Migration): |
|
||||
|
|
||||
dependencies = [ |
|
||||
('chatbot', '0001_initial'), |
|
||||
] |
|
||||
|
|
||||
operations = [ |
|
||||
migrations.AlterModelOptions( |
|
||||
name='messagehistory', |
|
||||
options={'ordering': ('-date_sent',), 'verbose_name': 'Message history', 'verbose_name_plural': 'Message histories'}, |
|
||||
), |
|
||||
migrations.AlterModelOptions( |
|
||||
name='messagequeue', |
|
||||
options={'ordering': ('target_employee__username',), 'verbose_name': 'Message queue', 'verbose_name_plural': 'Message queue'}, |
|
||||
), |
|
||||
migrations.AlterModelOptions( |
|
||||
name='telegrambot', |
|
||||
options={'ordering': ('chat_id',), 'verbose_name': 'Telegram bot', 'verbose_name_plural': 'Telegram bots'}, |
|
||||
), |
|
||||
] |
|
||||
@ -1,73 +0,0 @@ |
|||||
from django.utils.translation import gettext_lazy as _ |
|
||||
from django.db import models |
|
||||
from django.conf import settings |
|
||||
|
|
||||
AUTH_USER_MODEL = getattr(settings, 'AUTH_USER_MODEL') |
|
||||
|
|
||||
|
|
||||
class ChatException(Exception): |
|
||||
pass |
|
||||
|
|
||||
|
|
||||
class TelegramBot(models.Model): |
|
||||
user = models.ForeignKey(AUTH_USER_MODEL, on_delete=models.CASCADE, verbose_name=_('Employee')) |
|
||||
chat_id = models.PositiveIntegerField(_('Telegram chat id'), default=0) |
|
||||
|
|
||||
def __str__(self): |
|
||||
return "%s - %d" % (self.user.get_full_name(), self.chat_id) |
|
||||
|
|
||||
class Meta: |
|
||||
db_table = 'chat_telegram_bot' |
|
||||
verbose_name = _('Telegram bot') |
|
||||
verbose_name_plural = _('Telegram bots') |
|
||||
ordering = ('chat_id',) |
|
||||
|
|
||||
|
|
||||
class MessageHistory(models.Model): |
|
||||
user = models.ForeignKey(AUTH_USER_MODEL, on_delete=models.CASCADE) |
|
||||
message = models.CharField(max_length=255) |
|
||||
date_sent = models.DateTimeField(auto_now_add=True) |
|
||||
|
|
||||
def __str__(self): |
|
||||
return self.message |
|
||||
|
|
||||
class Meta: |
|
||||
db_table = 'chat_message_history' |
|
||||
verbose_name = _('Message history') |
|
||||
verbose_name_plural = _('Message histories') |
|
||||
ordering = ('-date_sent',) |
|
||||
|
|
||||
|
|
||||
class MessageQueueManager(models.Manager): |
|
||||
def pop(self, user, tag='none'): |
|
||||
msgs = self.filter(target_employee=user, status='n', tag=tag)[:1].only('message').values('id', 'message') |
|
||||
if len(msgs) > 0: |
|
||||
self.filter(id=msgs[0]['id']).delete() |
|
||||
return msgs[0]['message'] |
|
||||
|
|
||||
def push(self, msg, user, tag='none'): |
|
||||
msg = self.create(target_employee=user, message=msg, tag=tag) |
|
||||
return msg |
|
||||
|
|
||||
|
|
||||
class MessageQueue(models.Model): |
|
||||
target_employee = models.ForeignKey(AUTH_USER_MODEL, on_delete=models.CASCADE, verbose_name=_('Target employee')) |
|
||||
message = models.CharField(_('Message'), max_length=255) |
|
||||
STATUSES = ( |
|
||||
('n', 'New'), |
|
||||
('r', 'Read') |
|
||||
) |
|
||||
status = models.CharField(_('Status of message'), max_length=1, choices=STATUSES, default='n') |
|
||||
# tag: each application puts its own to separate messages between these applications |
|
||||
tag = models.CharField(_('App tag'), max_length=6, default='none') |
|
||||
|
|
||||
objects = MessageQueueManager() |
|
||||
|
|
||||
def __str__(self): |
|
||||
return self.message |
|
||||
|
|
||||
class Meta: |
|
||||
db_table = 'chat_message_queue' |
|
||||
verbose_name = _('Message queue') |
|
||||
verbose_name_plural = _('Message queue') |
|
||||
ordering = ('target_employee__username',) |
|
||||
@ -1,149 +0,0 @@ |
|||||
# -*- coding: utf-8 -*- |
|
||||
from telepot import helper, glance, Bot |
|
||||
from telepot.exception import TelegramError |
|
||||
import os |
|
||||
import socket |
|
||||
import collections |
|
||||
from django.utils.translation import ugettext as _ |
|
||||
from urllib3.exceptions import ProtocolError |
|
||||
from .models import TelegramBot, ChatException, MessageQueue |
|
||||
from chatbot.models import MessageHistory |
|
||||
from accounts_app.models import UserProfile |
|
||||
from django.conf import settings |
|
||||
|
|
||||
token = getattr(settings, 'TELEGRAM_BOT_TOKEN') |
|
||||
|
|
||||
|
|
||||
class DjingTelebot(helper.ChatHandler): |
|
||||
_current_user = None |
|
||||
_dialog_fn = None |
|
||||
_chat_id = 0 |
|
||||
|
|
||||
def __init__(self, seed_tuple, **kwargs): |
|
||||
super(DjingTelebot, self).__init__(seed_tuple, **kwargs) |
|
||||
self.cmds = { |
|
||||
'ping': self.ping, |
|
||||
'iam': self.say_me |
|
||||
} |
|
||||
|
|
||||
# отвечаем пользователю |
|
||||
def _sent_reply(self, text): |
|
||||
self.sender.sendMessage(text) |
|
||||
|
|
||||
# задаём вопрос пользователю, и ожидаем ответ в fn |
|
||||
def _question(self, text, fn): |
|
||||
if not isinstance(fn, collections.Callable): |
|
||||
raise TypeError |
|
||||
self._dialog_fn = fn |
|
||||
if text is not None: |
|
||||
self._sent_reply(text) |
|
||||
|
|
||||
# сохраняем сообщение в базе |
|
||||
def _message_log(self, msg): |
|
||||
if self._current_user is None: |
|
||||
self._question(None, self.question_name) |
|
||||
return False |
|
||||
MessageHistory.objects.create( |
|
||||
user=self._current_user, |
|
||||
message=msg |
|
||||
) |
|
||||
return True |
|
||||
|
|
||||
# Начинаем диалог |
|
||||
# @seed - chat_id |
|
||||
def open(self, initial_msg, seed): |
|
||||
content_type, chat_type, chat_id = glance(initial_msg) |
|
||||
if content_type != 'text': |
|
||||
return True |
|
||||
self._chat_id = chat_id |
|
||||
try: |
|
||||
tbot = TelegramBot.objects.get(chat_id=seed) |
|
||||
self._current_user = tbot.user |
|
||||
self._message_log(initial_msg['text']) |
|
||||
except TelegramBot.DoesNotExist: |
|
||||
self._question(_("Let's get acquainted, what is your name? Write your login from billing."), |
|
||||
self.question_name) |
|
||||
return True # prevent on_message() from being called on the initial message |
|
||||
|
|
||||
# получаем сообщение |
|
||||
def on_chat_message(self, msg): |
|
||||
content_type, chat_type, chat_id = glance(msg) |
|
||||
if content_type != 'text': |
|
||||
return |
|
||||
self._chat_id = chat_id |
|
||||
text = msg['text'].lower() |
|
||||
|
|
||||
# выполняем комманды если они есть |
|
||||
if text in self.cmds.keys(): |
|
||||
self.cmds[text]() |
|
||||
elif self._dialog_fn is not None: |
|
||||
if not callable(self._dialog_fn): |
|
||||
raise TypeError |
|
||||
self._dialog_fn(text) |
|
||||
self._dialog_fn = None |
|
||||
else: |
|
||||
self._sent_reply(_('I do not know the answer to this yet.')) |
|
||||
|
|
||||
if not self._message_log(text): |
|
||||
return |
|
||||
|
|
||||
# спрашиваем имя пользователя |
|
||||
def question_name(self, username): |
|
||||
try: |
|
||||
profile = UserProfile.objects.get(username=username) |
|
||||
self._current_user = profile |
|
||||
try: |
|
||||
TelegramBot.objects.get(user=profile) |
|
||||
except TelegramBot.DoesNotExist: |
|
||||
if self._chat_id == 0: |
|
||||
raise ChatException('telebot.py. def question_name: Chat id is empty') |
|
||||
TelegramBot.objects.create( |
|
||||
user=profile, |
|
||||
chat_id=self._chat_id |
|
||||
) |
|
||||
except UserProfile.DoesNotExist: |
|
||||
self._question( |
|
||||
_("You are not found in the database, check that it correctly pointed out its LOGIN. Try again"), |
|
||||
self.question_name) |
|
||||
return |
|
||||
self._sent_reply( |
|
||||
_("Yes, it's nice to meet %(username)s, I will notify you about events in billing. Successful work ;)") |
|
||||
% {'username': profile.get_full_name()}) |
|
||||
|
|
||||
# заканчивается время диалога |
|
||||
# ex - время ожидания (timeout=ex в pave_event_space) |
|
||||
def on_close(self, ex): |
|
||||
self._current_user = None |
|
||||
self._dialog_fn = None |
|
||||
self._chat_id = 0 |
|
||||
|
|
||||
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) |
|
||||
return |
|
||||
try: |
|
||||
socket.inet_aton(ip) |
|
||||
ret = os.popen('`which ping` -c 10 %s' % ip).read() |
|
||||
self._sent_reply(ret) |
|
||||
except socket.error: |
|
||||
self._question(_("It's not like ip address, try again"), self.ping) |
|
||||
|
|
||||
def say_me(self): |
|
||||
self._sent_reply(_("You're '%s', right?") % self._current_user.get_full_name()) |
|
||||
|
|
||||
|
|
||||
# Just sending text to specified account |
|
||||
def send_notify(msg_text, account, tag='none'): |
|
||||
try: |
|
||||
MessageQueue.objects.push(msg=msg_text, user=account, tag=tag) |
|
||||
if token is None: |
|
||||
raise ChatException(_('Telegram bot token not found')) |
|
||||
tb = TelegramBot.objects.get(user=account) |
|
||||
tbot = Bot(token) |
|
||||
tbot.sendMessage(tb.chat_id, msg_text) |
|
||||
except TelegramBot.DoesNotExist: |
|
||||
raise ChatException(_("Recipient '%s' does not subscribed on notifications") % account.get_full_name()) |
|
||||
except ProtocolError as e: |
|
||||
raise ChatException('ProtocolError: %s' % e) |
|
||||
except TelegramError as e: |
|
||||
raise ChatException('Telegram error: %s' % e) |
|
||||
@ -1,3 +0,0 @@ |
|||||
from django.shortcuts import render |
|
||||
|
|
||||
# Create your views here. |
|
||||
@ -0,0 +1,120 @@ |
|||||
|
# Generated by Django 2.1.1 on 2019-03-05 19:32 |
||||
|
|
||||
|
from django.db import migrations, models |
||||
|
import django.db.models.deletion |
||||
|
import djing.fields |
||||
|
import jsonfield.fields |
||||
|
|
||||
|
|
||||
|
class Migration(migrations.Migration): |
||||
|
replaces = [ |
||||
|
('devapp', '0001_initial'), |
||||
|
('devapp', '0002_auto_20180409_1318'), |
||||
|
('devapp', '0003_auto_20180529_1311'), |
||||
|
('devapp', '0004_device_extra_data'), |
||||
|
('devapp', '0005_device_ip_address_change') |
||||
|
] |
||||
|
|
||||
|
initial = True |
||||
|
|
||||
|
dependencies = [ |
||||
|
('group_app', '0001_initial'), |
||||
|
] |
||||
|
|
||||
|
operations = [ |
||||
|
migrations.CreateModel( |
||||
|
name='Device', |
||||
|
fields=[ |
||||
|
('id', models.AutoField( |
||||
|
auto_created=True, primary_key=True, |
||||
|
serialize=False, verbose_name='ID' |
||||
|
)), |
||||
|
('ip_address', models.GenericIPAddressField( |
||||
|
blank=True, null=True, verbose_name='Ip address' |
||||
|
)), |
||||
|
('mac_addr', djing.fields.MACAddressField( |
||||
|
blank=True, integer=True, |
||||
|
null=True, unique=True, |
||||
|
verbose_name='Mac address' |
||||
|
)), |
||||
|
('comment', models.CharField( |
||||
|
max_length=256, verbose_name='Comment' |
||||
|
)), |
||||
|
('devtype', models.CharField( |
||||
|
choices=[('Dl', 'DLink switch'), ('Pn', 'PON OLT'), |
||||
|
('On', 'PON ONU BDCOM'), ('Ex', 'Eltex switch'), |
||||
|
('Zt', 'OLT ZTE C320'), ('Zo', 'Zte ONU F660'), |
||||
|
('Z6', 'Zte ONU F601'), ('Hw', 'Huawei switch')], |
||||
|
default='Dl', max_length=2, |
||||
|
verbose_name='Device type' |
||||
|
)), |
||||
|
('man_passw', models.CharField( |
||||
|
blank=True, max_length=16, |
||||
|
null=True, verbose_name='SNMP password' |
||||
|
)), |
||||
|
('status', models.CharField( |
||||
|
choices=[('und', 'Undefined'), ('up', 'Up'), |
||||
|
('unr', 'Unreachable'), ('dwn', 'Down')], |
||||
|
default='und', max_length=3, |
||||
|
verbose_name='Status' |
||||
|
)), |
||||
|
('is_noticeable', models.BooleanField( |
||||
|
default=False, |
||||
|
verbose_name='Send notify when monitoring state changed' |
||||
|
)), |
||||
|
('group', models.ForeignKey( |
||||
|
blank=True, null=True, |
||||
|
on_delete=django.db.models.deletion.SET_NULL, |
||||
|
to='group_app.Group', verbose_name='Device group' |
||||
|
)), |
||||
|
('parent_dev', models.ForeignKey( |
||||
|
blank=True, null=True, |
||||
|
on_delete=django.db.models.deletion.SET_NULL, |
||||
|
to='devapp.Device', verbose_name='Parent device' |
||||
|
)), |
||||
|
('extra_data', jsonfield.fields.JSONField( |
||||
|
blank=True, |
||||
|
help_text='Extra data in JSON format. You may use it for your custom data', |
||||
|
null=True, verbose_name='Extra data' |
||||
|
)), |
||||
|
('snmp_extra', models.CharField( |
||||
|
blank=True, max_length=256, |
||||
|
null=True, verbose_name='SNMP extra info' |
||||
|
)) |
||||
|
], |
||||
|
options={ |
||||
|
'verbose_name': 'Device', |
||||
|
'verbose_name_plural': 'Devices', |
||||
|
'db_table': 'dev', |
||||
|
'ordering': ('id',), |
||||
|
}, |
||||
|
), |
||||
|
migrations.CreateModel( |
||||
|
name='Port', |
||||
|
fields=[ |
||||
|
('id', models.AutoField( |
||||
|
auto_created=True, primary_key=True, |
||||
|
serialize=False, verbose_name='ID' |
||||
|
)), |
||||
|
('num', models.PositiveSmallIntegerField( |
||||
|
default=0, verbose_name='Number' |
||||
|
)), |
||||
|
('descr', models.CharField( |
||||
|
blank=True, max_length=60, null=True, |
||||
|
verbose_name='Description' |
||||
|
)), |
||||
|
('device', models.ForeignKey( |
||||
|
on_delete=django.db.models.deletion.CASCADE, |
||||
|
to='devapp.Device', verbose_name='Device' |
||||
|
)), |
||||
|
], |
||||
|
options={ |
||||
|
'verbose_name': 'Port', |
||||
|
'verbose_name_plural': 'Ports', |
||||
|
'db_table': 'dev_port', |
||||
|
'permissions': (('can_toggle_ports', 'Can toggle ports'),), |
||||
|
'ordering': ('num',), |
||||
|
'unique_together': {('device', 'num')}, |
||||
|
}, |
||||
|
), |
||||
|
] |
||||
@ -0,0 +1,6 @@ |
|||||
|
from .f601 import register_onu as register_f601_onu |
||||
|
from .f660 import register_onu as register_f660_onu |
||||
|
from .base import ( |
||||
|
ZteOltConsoleError, OnuZteRegisterError, |
||||
|
ZTEFiberIsFull, ZteOltLoginFailed, ExpectValidationError |
||||
|
) |
||||
@ -0,0 +1,86 @@ |
|||||
|
import re |
||||
|
import sys |
||||
|
from pexpect import spawn |
||||
|
|
||||
|
|
||||
|
class ZteOltConsoleError(Exception): |
||||
|
pass |
||||
|
|
||||
|
|
||||
|
class OnuZteRegisterError(ZteOltConsoleError): |
||||
|
pass |
||||
|
|
||||
|
|
||||
|
class ZTEFiberIsFull(ZteOltConsoleError): |
||||
|
pass |
||||
|
|
||||
|
|
||||
|
class ZteOltLoginFailed(ZteOltConsoleError): |
||||
|
pass |
||||
|
|
||||
|
|
||||
|
class ExpectValidationError(ValueError): |
||||
|
pass |
||||
|
|
||||
|
|
||||
|
class MySpawn(spawn): |
||||
|
def __init__(self, *args, **kwargs): |
||||
|
super(MySpawn, self).__init__(encoding='utf-8', *args, **kwargs) |
||||
|
self.logfile = sys.stdout |
||||
|
|
||||
|
def do_cmd(self, c, prompt): |
||||
|
self.sendline(c) |
||||
|
return self.expect_exact(prompt) |
||||
|
|
||||
|
def get_lines(self): |
||||
|
return self.buffer.split('\r\n') |
||||
|
|
||||
|
def get_lines_before(self): |
||||
|
return self.before.split('\r\n') |
||||
|
|
||||
|
|
||||
|
def parse_onu_name(onu_name: str, name_regexp=re.compile('[/:_]')): |
||||
|
gpon_onu, stack_num, rack_num, fiber_num, onu_num = name_regexp.split(onu_name) |
||||
|
return { |
||||
|
'stack_num': stack_num, |
||||
|
'rack_num': rack_num, |
||||
|
'fiber_num': fiber_num, |
||||
|
'onu_num': onu_num |
||||
|
} |
||||
|
|
||||
|
|
||||
|
def get_unregistered_onu(lines, serial): |
||||
|
for line in lines: |
||||
|
if line.startswith('gpon-onu_'): |
||||
|
spls = re.split(r'\s+', line) |
||||
|
if len(spls) > 2: |
||||
|
if serial == spls[1]: |
||||
|
onu_index, sn, state = spls[:3] |
||||
|
return parse_onu_name(onu_index) |
||||
|
|
||||
|
|
||||
|
def get_free_registered_onu_number(lines): |
||||
|
onu_type_regexp = re.compile(r'^\s{1,5}onu \d{1,3} type [-\w\d]{4,64} sn \w{4,64}$') |
||||
|
onu_olt_num = None |
||||
|
i = 0 |
||||
|
for l in lines: |
||||
|
if onu_type_regexp.match(l): |
||||
|
# match line |
||||
|
i += 1 |
||||
|
onu, num, onu_type, onu_type, sn, onu_sn = l.split() |
||||
|
onu_olt_num = int(num) |
||||
|
if onu_olt_num > i: |
||||
|
return i |
||||
|
return onu_olt_num + 1 |
||||
|
|
||||
|
|
||||
|
def sn_to_mac(sn: str): |
||||
|
t = sn[4:].lower() |
||||
|
r = tuple(t[i:i + 2] for i in range(0, len(t), 2)) |
||||
|
return '45:47:%s' % ':'.join(r) |
||||
|
|
||||
|
|
||||
|
def onu_conv(rack_num: int, fiber_num: int, port_num: int): |
||||
|
r = "10000{0:08b}{1:08b}00000000".format(rack_num, fiber_num) |
||||
|
snmp_fiber_num = int(r, base=2) |
||||
|
return "%d.%d" % (snmp_fiber_num, port_num) |
||||
@ -0,0 +1,165 @@ |
|||||
|
import re |
||||
|
from typing import Optional |
||||
|
from djing.lib import process_lock |
||||
|
from . import base |
||||
|
|
||||
|
|
||||
|
def get_onu_template(vlan_id: int, mac_addr: str): |
||||
|
template = ( |
||||
|
'sn-bind enable sn', |
||||
|
'tcont 1 profile HSI_100', |
||||
|
'gemport 1 unicast tcont 1 dir both', |
||||
|
'switchport mode hybrid vport 1', |
||||
|
'switchport vlan %d tag vport 1' % vlan_id, |
||||
|
'port-location format flexible-syntax vport 1', |
||||
|
'port-location sub-option remote-id enable vport 1', |
||||
|
'port-location sub-option remote-id name %s vport 1' % mac_addr, |
||||
|
'dhcp-option82 enable vport 1', |
||||
|
'dhcp-option82 trust true replace vport 1', |
||||
|
'ip dhcp snooping enable vport 1' |
||||
|
) |
||||
|
return template |
||||
|
|
||||
|
|
||||
|
def get_pon_mng_template(vlan_id: int): |
||||
|
template = ( |
||||
|
'service HSI type internet gemport 1 vlan %d' % vlan_id, |
||||
|
'loop-detect ethuni eth_0/1 enable', |
||||
|
'vlan port eth_0/1 mode tag vlan %d' % vlan_id, |
||||
|
'dhcp-ip ethuni eth_0/1 from-internet' |
||||
|
) |
||||
|
return template |
||||
|
|
||||
|
|
||||
|
def appy_config(onu_mac: str, sn: str, hostname: str, login: str, password: str, prompt: str, vlan: int): |
||||
|
onu_type = 'ZTE-F601' |
||||
|
|
||||
|
# Входим |
||||
|
ch = base.MySpawn('telnet %s' % hostname) |
||||
|
ch.timeout = 15 |
||||
|
ch.expect_exact('Username:') |
||||
|
ch.do_cmd(login, 'Password:') |
||||
|
|
||||
|
choice = ch.do_cmd(password, ['bad password.', '%s#' % prompt]) |
||||
|
if choice == 0: |
||||
|
raise base.ZteOltLoginFailed |
||||
|
|
||||
|
ch.do_cmd('terminal length 0', '%s#' % prompt) |
||||
|
choice = ch.do_cmd('show gpon onu uncfg', ['No related information to show', '%s#' % prompt]) |
||||
|
|
||||
|
if choice == 0: |
||||
|
ch.close() |
||||
|
raise base.OnuZteRegisterError('unregistered onu not found, sn=%s' % sn) |
||||
|
elif choice == 1: |
||||
|
# Получим незареганные onu |
||||
|
unregistered_onu = base.get_unregistered_onu( |
||||
|
lines=ch.get_lines_before(), |
||||
|
serial=sn |
||||
|
) |
||||
|
if unregistered_onu is None: |
||||
|
ch.close() |
||||
|
raise base.OnuZteRegisterError('unregistered onu not found, sn=%s' % sn) |
||||
|
|
||||
|
stack_num = int(unregistered_onu.get('stack_num')) |
||||
|
rack_num = int(unregistered_onu.get('rack_num')) |
||||
|
fiber_num = int(unregistered_onu.get('fiber_num')) |
||||
|
|
||||
|
# Получим последнюю зарегистрированную onu |
||||
|
ch.do_cmd('show run int gpon-olt_%(stack)s/%(rack)s/%(fiber)s' % { |
||||
|
'stack': stack_num, |
||||
|
'rack': rack_num, |
||||
|
'fiber': fiber_num |
||||
|
}, '%s#' % prompt) |
||||
|
free_onu_number = base.get_free_registered_onu_number( |
||||
|
ch.get_lines_before() |
||||
|
) |
||||
|
|
||||
|
if free_onu_number > 126: |
||||
|
ch.close() |
||||
|
raise base.ZTEFiberIsFull('olt fiber %d is full' % fiber_num) |
||||
|
|
||||
|
# enter to config |
||||
|
ch.do_cmd('conf t', '%s(config)#' % prompt) |
||||
|
|
||||
|
int_addr = '%d/%d/%d' % ( |
||||
|
stack_num, |
||||
|
rack_num, |
||||
|
fiber_num |
||||
|
) |
||||
|
|
||||
|
# go to olt interface |
||||
|
ch.do_cmd('interface gpon-olt_%s' % int_addr, '%s(config-if)#' % prompt) |
||||
|
|
||||
|
# register onu on olt interface |
||||
|
ch.do_cmd('onu %d type %s sn %s' % ( |
||||
|
free_onu_number, |
||||
|
onu_type, |
||||
|
sn |
||||
|
), '%s(config-if)#' % prompt) |
||||
|
|
||||
|
# Exit from int olt |
||||
|
ch.do_cmd('exit', '%s(config)#' % prompt) |
||||
|
|
||||
|
# Enter to int onu |
||||
|
ch.do_cmd('int gpon-onu_%(int_addr)s:%(onu_num)d' % { |
||||
|
'int_addr': int_addr, |
||||
|
'onu_num': free_onu_number |
||||
|
}, '%s(config-if)#' % prompt) |
||||
|
|
||||
|
# Apply int onu config |
||||
|
template = get_onu_template(vlan, onu_mac) |
||||
|
for line in template: |
||||
|
ch.do_cmd(line, '%s(config-if)#' % prompt) |
||||
|
|
||||
|
# Exit |
||||
|
ch.do_cmd('exit', '%s(config)#' % prompt) |
||||
|
|
||||
|
# Enter to pon-onu-mng |
||||
|
ch.do_cmd('pon-onu-mng gpon-onu_%(int_addr)s:%(onu_num)d' % { |
||||
|
'int_addr': int_addr, |
||||
|
'onu_num': free_onu_number |
||||
|
}, '%s(gpon-onu-mng)#' % prompt) |
||||
|
|
||||
|
# Apply config to pon-onu-mng |
||||
|
for line in get_pon_mng_template(vlan): |
||||
|
ch.do_cmd(line, '%s(gpon-onu-mng)#' % prompt) |
||||
|
|
||||
|
# Exit |
||||
|
ch.do_cmd('exit', '%s(config)#' % prompt) |
||||
|
|
||||
|
ch.close() |
||||
|
return base.onu_conv( |
||||
|
rack_num=rack_num, |
||||
|
fiber_num=fiber_num, |
||||
|
port_num=free_onu_number |
||||
|
) |
||||
|
else: |
||||
|
ch.close() |
||||
|
raise base.ZteOltConsoleError("I don't know what choice:", choice) |
||||
|
|
||||
|
|
||||
|
# Main Entry point |
||||
|
@process_lock |
||||
|
def register_onu(onu_mac: Optional[str], serial: str, zte_ip_addr: str, telnet_login: str, |
||||
|
telnet_passw: str, telnet_prompt: str, onu_vlan: int): |
||||
|
|
||||
|
if not re.match(r'^ZTEG[0-9A-F]{8}$', serial): |
||||
|
raise base.ExpectValidationError('Serial not valid, match: ^ZTEG[0-9A-F]{8}$') |
||||
|
|
||||
|
if not isinstance(onu_vlan, int): |
||||
|
onu_vlan = int(onu_vlan) |
||||
|
|
||||
|
if onu_mac is None: |
||||
|
onu_mac = base.sn_to_mac(serial) |
||||
|
|
||||
|
IP4_ADDR_REGEX = ( |
||||
|
r'^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.' |
||||
|
r'(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.' |
||||
|
r'(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.' |
||||
|
r'(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$' |
||||
|
) |
||||
|
if not re.match(IP4_ADDR_REGEX, zte_ip_addr): |
||||
|
raise base.ExpectValidationError('ip address for zte not valid') |
||||
|
|
||||
|
return appy_config(onu_mac, serial, zte_ip_addr, telnet_login, |
||||
|
telnet_passw, telnet_prompt, onu_vlan) |
||||
@ -0,0 +1,141 @@ |
|||||
|
import re |
||||
|
from typing import Optional |
||||
|
|
||||
|
from djing.lib import process_lock |
||||
|
from . import base |
||||
|
|
||||
|
|
||||
|
def get_onu_template(vlan_id: int, mac_addr: str): |
||||
|
template = ( |
||||
|
'switchport mode hybrid vport 1', |
||||
|
'switchport vlan %d tag vport 1' % vlan_id, |
||||
|
'port-location format flexible-syntax vport 1', |
||||
|
'port-location sub-option remote-id enable vport 1', |
||||
|
'port-location sub-option remote-id name %s vport 1' % mac_addr, |
||||
|
'dhcp-option82 enable vport 1', |
||||
|
'dhcp-option82 trust true replace vport 1', |
||||
|
'ip dhcp snooping enable vport 1' |
||||
|
) |
||||
|
return template |
||||
|
|
||||
|
|
||||
|
def appy_config(onu_mac: str, sn: str, hostname: str, login: str, password: str, prompt: str, vlan: int): |
||||
|
onu_type = 'ZTE-F660' |
||||
|
|
||||
|
# Входим |
||||
|
ch = base.MySpawn('telnet %s' % hostname) |
||||
|
ch.timeout = 15 |
||||
|
ch.expect_exact('Username:') |
||||
|
ch.do_cmd(login, 'Password:') |
||||
|
|
||||
|
choice = ch.do_cmd(password, ['bad password.', '%s#' % prompt]) |
||||
|
if choice == 0: |
||||
|
raise base.ZteOltLoginFailed |
||||
|
|
||||
|
ch.do_cmd('terminal length 0', '%s#' % prompt) |
||||
|
choice = ch.do_cmd('show gpon onu uncfg', ['No related information to show', '%s#' % prompt]) |
||||
|
if choice == 0: |
||||
|
ch.close() |
||||
|
raise base.OnuZteRegisterError('unregistered onu not found, sn=%s' % sn) |
||||
|
elif choice == 1: |
||||
|
# Получим незареганные onu |
||||
|
unregistered_onu = base.get_unregistered_onu( |
||||
|
lines=ch.get_lines_before(), |
||||
|
serial=sn |
||||
|
) |
||||
|
if unregistered_onu is None: |
||||
|
ch.close() |
||||
|
raise base.OnuZteRegisterError('unregistered onu not found, sn=%s' % sn) |
||||
|
stack_num = int(unregistered_onu.get('stack_num')) |
||||
|
rack_num = int(unregistered_onu.get('rack_num')) |
||||
|
fiber_num = int(unregistered_onu.get('fiber_num')) |
||||
|
|
||||
|
# Получим последнюю зарегистрированную onu |
||||
|
ch.do_cmd('show run int gpon-olt_%(stack)s/%(rack)s/%(fiber)s' % { |
||||
|
'stack': stack_num, |
||||
|
'rack': rack_num, |
||||
|
'fiber': fiber_num |
||||
|
}, '%s#' % prompt) |
||||
|
free_onu_number = base.get_free_registered_onu_number( |
||||
|
ch.get_lines_before() |
||||
|
) |
||||
|
if free_onu_number > 126: |
||||
|
ch.close() |
||||
|
raise base.ZTEFiberIsFull('olt fiber %d is full' % fiber_num) |
||||
|
|
||||
|
# enter to config |
||||
|
ch.do_cmd('conf t', '%s(config)#' % prompt) |
||||
|
int_addr = '%d/%d/%d' % ( |
||||
|
stack_num, |
||||
|
rack_num, |
||||
|
fiber_num |
||||
|
) |
||||
|
|
||||
|
# go to olt interface |
||||
|
ch.do_cmd('interface gpon-olt_%s' % int_addr, '%s(config-if)#' % prompt) |
||||
|
|
||||
|
# register onu on olt interface |
||||
|
ch.do_cmd('onu %d type %s sn %s' % ( |
||||
|
free_onu_number, |
||||
|
onu_type, |
||||
|
sn |
||||
|
), '%s(config-if)#' % prompt) |
||||
|
# register onu profile on olt interface |
||||
|
ch.do_cmd( |
||||
|
'onu %d profile line ZTE-F660-LINE remote ZTE-F660-ROUTER' % free_onu_number, |
||||
|
'%s(config-if)#' % prompt |
||||
|
) |
||||
|
|
||||
|
# Exit from int olt |
||||
|
ch.do_cmd('exit', '%s(config)#' % prompt) |
||||
|
|
||||
|
# Enter to int onu |
||||
|
ch.do_cmd('int gpon-onu_%(int_addr)s:%(onu_num)d' % { |
||||
|
'int_addr': int_addr, |
||||
|
'onu_num': free_onu_number |
||||
|
}, '%s(config-if)#' % prompt) |
||||
|
|
||||
|
# Apply int onu config |
||||
|
template = get_onu_template(vlan, onu_mac) |
||||
|
for line in template: |
||||
|
ch.do_cmd(line, '%s(config-if)#' % prompt) |
||||
|
|
||||
|
# Exit |
||||
|
ch.do_cmd('exit', '%s(config)#' % prompt) |
||||
|
ch.do_cmd('exit', '%s#' % prompt) |
||||
|
ch.close() |
||||
|
return base.onu_conv( |
||||
|
rack_num=rack_num, |
||||
|
fiber_num=fiber_num, |
||||
|
port_num=free_onu_number |
||||
|
) |
||||
|
else: |
||||
|
ch.close() |
||||
|
raise base.ZteOltConsoleError("I don't know what choice:", choice) |
||||
|
|
||||
|
|
||||
|
# Main Entry point |
||||
|
@process_lock |
||||
|
def register_onu(onu_mac: Optional[str], serial: str, zte_ip_addr: str, telnet_login: str, |
||||
|
telnet_passw: str, telnet_prompt: str, onu_vlan: int): |
||||
|
|
||||
|
if not re.match(r'^ZTEG[0-9A-F]{8}$', serial): |
||||
|
raise base.ExpectValidationError('Serial not valid, match: ^ZTEG[0-9A-F]{8}$') |
||||
|
|
||||
|
if not isinstance(onu_vlan, int): |
||||
|
onu_vlan = int(onu_vlan) |
||||
|
|
||||
|
if onu_mac is None: |
||||
|
onu_mac = base.sn_to_mac(serial) |
||||
|
|
||||
|
IP4_ADDR_REGEX = ( |
||||
|
r'^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.' |
||||
|
r'(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.' |
||||
|
r'(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.' |
||||
|
r'(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$' |
||||
|
) |
||||
|
if not re.match(IP4_ADDR_REGEX, zte_ip_addr): |
||||
|
raise base.ExpectValidationError('ip address for zte not valid') |
||||
|
|
||||
|
return appy_config(onu_mac, serial, zte_ip_addr, telnet_login, |
||||
|
telnet_passw, telnet_prompt, onu_vlan) |
||||
@ -1,4 +0,0 @@ |
|||||
from .tln import * |
|
||||
|
|
||||
__all__ = ('TelnetApi', 'ValidationError', 'ZTEFiberIsFull', 'ZteOltLoginFailed', |
|
||||
'OnuZteRegisterError', 'ZteOltConsoleError', 'register_onu_ZTE_F660') |
|
||||
@ -1,274 +0,0 @@ |
|||||
#!/usr/bin/env python3 |
|
||||
import re |
|
||||
import struct |
|
||||
from telnetlib import Telnet |
|
||||
from time import sleep |
|
||||
from typing import Generator, Dict, Optional, Tuple |
|
||||
|
|
||||
from djing.lib import process_lock |
|
||||
|
|
||||
|
|
||||
class ZteOltConsoleError(Exception): |
|
||||
pass |
|
||||
|
|
||||
|
|
||||
class OnuZteRegisterError(ZteOltConsoleError): |
|
||||
pass |
|
||||
|
|
||||
|
|
||||
class ZTEFiberIsFull(ZteOltConsoleError): |
|
||||
pass |
|
||||
|
|
||||
|
|
||||
class ZteOltLoginFailed(ZteOltConsoleError): |
|
||||
pass |
|
||||
|
|
||||
|
|
||||
class ValidationError(ValueError): |
|
||||
pass |
|
||||
|
|
||||
|
|
||||
MAC_ADDR_REGEX = b'^([0-9A-Fa-f]{1,2}[:-]){5}([0-9A-Fa-f]{1,2})$' |
|
||||
IP_ADDR_REGEX = ( |
|
||||
'^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.' |
|
||||
'(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.' |
|
||||
'(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.' |
|
||||
'(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$' |
|
||||
) |
|
||||
ONU_SN_REGEX = b'^ZTEG[A-F\d]{8}$' |
|
||||
|
|
||||
|
|
||||
class TelnetApi(Telnet): |
|
||||
|
|
||||
def __init__(self, prompt_string: bytes, *args, **kwargs): |
|
||||
timeout = kwargs.get('timeout') |
|
||||
if timeout: |
|
||||
self._timeout = timeout |
|
||||
self._prompt_string = prompt_string or b'ZTE#' |
|
||||
self.config_level = [] |
|
||||
super().__init__(*args, **kwargs) |
|
||||
|
|
||||
def write(self, buffer: bytes) -> None: |
|
||||
buffer = buffer + b'\n' |
|
||||
print('>>', buffer) |
|
||||
super().write(buffer) |
|
||||
|
|
||||
def resize_screen(self, width: int, height: int): |
|
||||
naws_cmd = struct.pack('>BBBHHBB', |
|
||||
255, 250, 31, # IAC SB NAWS |
|
||||
width, height, |
|
||||
255, 240 # IAC SE |
|
||||
) |
|
||||
sock = self.get_socket() |
|
||||
sock.send(naws_cmd) |
|
||||
|
|
||||
def read_lines(self) -> Generator: |
|
||||
while True: |
|
||||
line = self.read_until(b'\r\n', timeout=self._timeout) |
|
||||
line = line.replace(b'\r\n', b'') |
|
||||
if self._prompt_string == line: |
|
||||
break |
|
||||
if line == b'': |
|
||||
continue |
|
||||
yield line |
|
||||
|
|
||||
def command_to(self, cmd: bytes) -> Generator: |
|
||||
self.write(cmd) |
|
||||
return self.read_lines() |
|
||||
|
|
||||
def set_prompt_string(self, prompt_string: bytes) -> None: |
|
||||
self.config_level.append(prompt_string) |
|
||||
self._prompt_string = prompt_string |
|
||||
|
|
||||
def level_exit(self) -> Optional[Tuple]: |
|
||||
if len(self.config_level) < 2: |
|
||||
print('We are in root') |
|
||||
return |
|
||||
self.config_level.pop() |
|
||||
self.set_prompt_string(self.config_level[-1]) |
|
||||
return tuple(self.command_to(b'exit')) |
|
||||
|
|
||||
def __del__(self): |
|
||||
if self.sock: |
|
||||
self.write(b'exit') |
|
||||
super().__del__() |
|
||||
|
|
||||
|
|
||||
def parse_onu_name(onu_name: bytes, name_regexp=re.compile(b'[/:_]')) -> Dict[str, bytes]: |
|
||||
gpon_onu, stack_num, rack_num, fiber_num, onu_num = name_regexp.split(onu_name) |
|
||||
return { |
|
||||
'stack_num': stack_num, |
|
||||
'rack_num': rack_num, |
|
||||
'fiber_num': fiber_num, |
|
||||
'onu_num': onu_num |
|
||||
} |
|
||||
|
|
||||
|
|
||||
class OltZTERegister(TelnetApi): |
|
||||
|
|
||||
def __init__(self, screen_size: Tuple[int, int], 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) |
|
||||
|
|
||||
def enter(self, username: bytes, passw: bytes) -> None: |
|
||||
self.read_until(b'Username:') |
|
||||
self.write(username) |
|
||||
self.read_until(b'Password:') |
|
||||
self.write(passw) |
|
||||
for l in self.read_lines(): |
|
||||
if b'bad password' in l: |
|
||||
raise ZteOltLoginFailed |
|
||||
|
|
||||
def get_unregistered_onu(self, sn: bytes) -> Optional[Dict]: |
|
||||
lines = tuple(self.command_to(b'show gpon onu uncfg')) |
|
||||
if len(lines) > 3: |
|
||||
# devices available |
|
||||
# find onu by sn |
|
||||
line = tuple(ln for ln in lines if sn.lower() in ln.lower()) |
|
||||
if len(line) > 0: |
|
||||
line = line[0] |
|
||||
onu_name, onu_sn, onu_state = line.split() |
|
||||
onu_numbers = parse_onu_name(onu_name) |
|
||||
onu_numbers.update({ |
|
||||
'onu_name': onu_name, |
|
||||
'onu_sn': onu_sn, |
|
||||
'onu_state': onu_state |
|
||||
}) |
|
||||
return onu_numbers |
|
||||
|
|
||||
def get_last_registered_onu_number(self, stack_num: int, rack_num: int, fiber_num: int) -> int: |
|
||||
registered_lines = self.command_to(b'show run int gpon-olt_%d/%d/%d' % ( |
|
||||
stack_num, |
|
||||
rack_num, |
|
||||
fiber_num |
|
||||
)) |
|
||||
onu_type_regexp = re.compile(b'^\s{2}onu \d{1,3} type [-\w\d]{4,64} sn \w{4,64}$') |
|
||||
last_onu = 0 |
|
||||
for rl in registered_lines: |
|
||||
if rl == b' --More--': |
|
||||
self.write(b' ') |
|
||||
if onu_type_regexp.match(rl): |
|
||||
_onu, num, _type, onu_type, _sn, onu_sn = rl.split() |
|
||||
last_onu = int(num) |
|
||||
return last_onu |
|
||||
|
|
||||
def enter_to_config_mode(self) -> bool: |
|
||||
prompt = b'%s(config)#' % self.prompt_title |
|
||||
self.set_prompt_string(prompt) |
|
||||
res = tuple(self.command_to(b'config terminal')) |
|
||||
if res[1].startswith(b'Enter configuration commands'): |
|
||||
# ok, we in the config mode |
|
||||
return True |
|
||||
return False |
|
||||
|
|
||||
def go_to_olt_interface(self, stack_num: int, rack_num: int, fiber_num: int) -> Tuple: |
|
||||
self.set_prompt_string(b'%s(config-if)#' % self.prompt_title) |
|
||||
return tuple(self.command_to(b'interface gpon-olt_%d/%d/%d' % ( |
|
||||
stack_num, |
|
||||
rack_num, |
|
||||
fiber_num |
|
||||
))) |
|
||||
|
|
||||
def go_to_onu_interface(self, stack_num: int, rack_num: int, fiber_num: int, onu_port_num: int) -> Tuple: |
|
||||
self.set_prompt_string(b'%s(config-if)#' % self.prompt_title) |
|
||||
return tuple(self.command_to(b'interface gpon-onu_%d/%d/%d:%d' % ( |
|
||||
stack_num, |
|
||||
rack_num, |
|
||||
fiber_num, |
|
||||
onu_port_num |
|
||||
))) |
|
||||
|
|
||||
def apply_conf_to_onu(self, mac_addr: bytes, vlan_id: int) -> None: |
|
||||
tmpl = ( |
|
||||
b'switchport vlan %d tag vport 1' % vlan_id, |
|
||||
b'port-location format flexible-syntax vport 1', |
|
||||
b'port-location sub-option remote-id enable vport 1', |
|
||||
b'port-location sub-option remote-id name %s vport 1' % mac_addr, |
|
||||
b'dhcp-option82 enable vport 1', |
|
||||
b'dhcp-option82 trust true replace vport 1', |
|
||||
b'ip dhcp snooping enable vport 1' |
|
||||
) |
|
||||
for conf_line in tmpl: |
|
||||
self.write(conf_line) |
|
||||
|
|
||||
def register_onu_on_olt_fiber(self, onu_type: bytes, new_onu_num: int, onu_sn: bytes, line_profile: bytes, |
|
||||
remote_profile: bytes) -> Tuple: |
|
||||
# ok, we in interface |
|
||||
tpl = b'onu %d type %s sn %s' % (new_onu_num, onu_type, onu_sn) |
|
||||
r = tuple(self.command_to(tpl)) |
|
||||
return tuple(self.command_to(b'onu %d profile line %s remote %s' % ( |
|
||||
new_onu_num, |
|
||||
line_profile, |
|
||||
remote_profile |
|
||||
))) + r |
|
||||
|
|
||||
|
|
||||
@process_lock |
|
||||
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' |
|
||||
line_profile = b'ZTE-F660-LINE' |
|
||||
remote_profile = b'ZTE-F660-ROUTER' |
|
||||
if not re.match(MAC_ADDR_REGEX, onu_mac): |
|
||||
raise ValidationError |
|
||||
if not re.match(IP_ADDR_REGEX, olt_ip): |
|
||||
raise ValidationError |
|
||||
if not re.match(ONU_SN_REGEX, onu_sn): |
|
||||
raise ValidationError |
|
||||
|
|
||||
tn = OltZTERegister(host=olt_ip, timeout=2, screen_size=(120, 128), prompt_title=prompt_title) |
|
||||
tn.enter(*login_passwd) |
|
||||
|
|
||||
unregistered_onu = tn.get_unregistered_onu(onu_sn) |
|
||||
if unregistered_onu is None: |
|
||||
raise OnuZteRegisterError('unregistered onu not found, sn=%s' % onu_sn.decode('utf-8')) |
|
||||
|
|
||||
stack_num = int(unregistered_onu['stack_num']) |
|
||||
rack_num = int(unregistered_onu['rack_num']) |
|
||||
fiber_num = int(unregistered_onu['fiber_num']) |
|
||||
|
|
||||
last_onu_number = tn.get_last_registered_onu_number( |
|
||||
stack_num, rack_num, fiber_num |
|
||||
) |
|
||||
|
|
||||
if last_onu_number > 126: |
|
||||
raise ZTEFiberIsFull('olt fiber %d is full' % fiber_num) |
|
||||
|
|
||||
# enter to config |
|
||||
if not tn.enter_to_config_mode(): |
|
||||
raise ZteOltConsoleError('Failed to enter to config mode') |
|
||||
|
|
||||
# go to olt interface |
|
||||
if not tn.go_to_olt_interface(stack_num, rack_num, fiber_num): |
|
||||
raise ZteOltConsoleError('Failed to enter in olt fiber port') |
|
||||
|
|
||||
# new onu port number |
|
||||
new_onu_port_num = last_onu_number + 1 |
|
||||
|
|
||||
# register onu on olt interface |
|
||||
r = tn.register_onu_on_olt_fiber(onu_type, new_onu_port_num, onu_sn, line_profile, remote_profile) |
|
||||
print(r) |
|
||||
|
|
||||
# exit from olt interface |
|
||||
tn.level_exit() |
|
||||
|
|
||||
r = tn.go_to_onu_interface(stack_num, rack_num, fiber_num, new_onu_port_num) |
|
||||
print(r) |
|
||||
|
|
||||
tn.apply_conf_to_onu(onu_mac, vlan_id) |
|
||||
sleep(1) |
|
||||
return stack_num, rack_num, fiber_num, new_onu_port_num |
|
||||
|
|
||||
|
|
||||
if __name__ == '__main__': |
|
||||
ip = '192.168.0.100' |
|
||||
try: |
|
||||
register_onu_ZTE_F660( |
|
||||
olt_ip=ip, onu_sn=b'ZTEG^#*$&@&', login_passwd=(b'login', b'password'), |
|
||||
onu_mac=b'MAC' |
|
||||
) |
|
||||
except ZteOltConsoleError as e: |
|
||||
print(e) |
|
||||
except ConnectionRefusedError: |
|
||||
print('ERROR: connection refused', ip) |
|
||||
@ -0,0 +1,36 @@ |
|||||
|
# Generated by Django 2.1.1 on 2019-03-05 12:47 |
||||
|
|
||||
|
from django.db import migrations, models |
||||
|
import ip_pool.fields |
||||
|
|
||||
|
|
||||
|
class Migration(migrations.Migration): |
||||
|
|
||||
|
replaces = [('ip_pool', '0001_initial'), ('ip_pool', '0002_change_unique'), ('ip_pool', '0003_auto_20181019_1230'), ('ip_pool', '0004_auto_20190305_1243')] |
||||
|
|
||||
|
initial = True |
||||
|
|
||||
|
dependencies = [ |
||||
|
('group_app', '0002_group_code'), |
||||
|
] |
||||
|
|
||||
|
operations = [ |
||||
|
migrations.CreateModel( |
||||
|
name='NetworkModel', |
||||
|
fields=[ |
||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
||||
|
('network', ip_pool.fields.GenericIpAddressWithPrefix(help_text='Ip address of network. For example: 192.168.1.0 or fde8:6789:1234:1::', unique=True, verbose_name='IP network')), |
||||
|
('kind', models.CharField(choices=[('inet', 'Internet'), ('guest', 'Guest'), ('trust', 'Trusted'), ('device', 'Devices'), ('admin', 'Admin')], default='guest', max_length=6, verbose_name='Kind of network')), |
||||
|
('description', models.CharField(max_length=64, verbose_name='Description')), |
||||
|
('ip_start', models.GenericIPAddressField(verbose_name='Start work ip range')), |
||||
|
('ip_end', models.GenericIPAddressField(verbose_name='End work ip range')), |
||||
|
('groups', models.ManyToManyField(to='group_app.Group', verbose_name='Groups')), |
||||
|
], |
||||
|
options={ |
||||
|
'verbose_name': 'Network', |
||||
|
'verbose_name_plural': 'Networks', |
||||
|
'db_table': 'ip_pool_network', |
||||
|
'ordering': ('network',), |
||||
|
}, |
||||
|
) |
||||
|
] |
||||
@ -0,0 +1,15 @@ |
|||||
|
# Generated by Django 2.1.1 on 2019-03-05 12:43 |
||||
|
|
||||
|
from django.db import migrations |
||||
|
|
||||
|
|
||||
|
class Migration(migrations.Migration): |
||||
|
|
||||
|
dependencies = [ |
||||
|
('ip_pool', '0003_auto_20181019_1230'), |
||||
|
] |
||||
|
|
||||
|
operations = [ |
||||
|
migrations.DeleteModel(name='IpLeaseModel'), |
||||
|
migrations.DeleteModel(name='LeasesHistory') |
||||
|
] |
||||
@ -0,0 +1 @@ |
|||||
|
default_app_config = 'messenger.apps.messengerConfig' |
||||
@ -0,0 +1,7 @@ |
|||||
|
from django.contrib import admin |
||||
|
from messenger import models |
||||
|
|
||||
|
admin.site.register(models.Messenger) |
||||
|
admin.site.register(models.ViberMessenger) |
||||
|
admin.site.register(models.ViberSubscriber) |
||||
|
admin.site.register(models.ViberMessage) |
||||
@ -0,0 +1,5 @@ |
|||||
|
from django.apps import AppConfig |
||||
|
|
||||
|
|
||||
|
class messengerConfig(AppConfig): |
||||
|
name = 'messenger' |
||||
@ -0,0 +1,28 @@ |
|||||
|
from django import forms |
||||
|
from messenger import models |
||||
|
|
||||
|
|
||||
|
class MessengerForm(forms.ModelForm): |
||||
|
class Meta: |
||||
|
model = models.Messenger |
||||
|
fields = ('bot_type',) |
||||
|
|
||||
|
|
||||
|
class MessengerViberForm(forms.ModelForm): |
||||
|
def __init__(self, *args, **kwargs): |
||||
|
kwargs['initial']['bot_type'] = 1 |
||||
|
super().__init__(*args, **kwargs) |
||||
|
inst = getattr(self, 'instance') |
||||
|
if inst: |
||||
|
self.fields['bot_type'].disabled = True |
||||
|
#self.fields['bot_type'].widget.attrs['disabled'] = True |
||||
|
|
||||
|
class Meta: |
||||
|
model = models.ViberMessenger |
||||
|
fields = '__all__' |
||||
|
|
||||
|
|
||||
|
class MessengerViberMessageForm(forms.ModelForm): |
||||
|
class Meta: |
||||
|
model = models.ViberMessage |
||||
|
fields = '__all__' |
||||
@ -0,0 +1,190 @@ |
|||||
|
# 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, 2019. |
||||
|
# |
||||
|
#, fuzzy |
||||
|
msgid "" |
||||
|
msgstr "" |
||||
|
"Report-Msgid-Bugs-To: \n" |
||||
|
"POT-Creation-Date: 2019-02-06 13:45+0300\n" |
||||
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" |
||||
|
"Last-Translator: Dmitry Novikov nerosketch@gmail.com\n" |
||||
|
"Language: ru\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" |
||||
|
|
||||
|
#: models.py:16 templates/messenger/messenger_list.html:20 |
||||
|
msgid "Title" |
||||
|
msgstr "Название" |
||||
|
|
||||
|
#: models.py:18 |
||||
|
msgid "Viber" |
||||
|
msgstr "Вайбер" |
||||
|
|
||||
|
#: models.py:20 |
||||
|
msgid "Bot type" |
||||
|
msgstr "Тип бота" |
||||
|
|
||||
|
#: models.py:21 templates/messenger/messenger_list.html:22 |
||||
|
msgid "Slug" |
||||
|
msgstr "Ссыль" |
||||
|
|
||||
|
#: models.py:28 models.py:92 |
||||
|
msgid "messenger" |
||||
|
msgstr "Мэссенджер" |
||||
|
|
||||
|
#: models.py:29 templates/messenger/messenger_list.html:7 |
||||
|
#: templates/messenger/messenger_list.html:12 |
||||
|
#: templates/messenger/vibermessenger_form.html:8 |
||||
|
msgid "Messengers" |
||||
|
msgstr "Мэссенджэры" |
||||
|
|
||||
|
#: models.py:48 |
||||
|
msgid "Bot secret token" |
||||
|
msgstr "Секретный токен viber" |
||||
|
|
||||
|
#: models.py:49 models.py:108 |
||||
|
msgid "Avatar" |
||||
|
msgstr "Аватар" |
||||
|
|
||||
|
#: models.py:83 |
||||
|
msgid "Viber messenger" |
||||
|
msgstr "Viber мэссенджэр" |
||||
|
|
||||
|
#: models.py:84 |
||||
|
msgid "Viber messengers" |
||||
|
msgstr "Viber мэссенджэры" |
||||
|
|
||||
|
#: models.py:89 |
||||
|
msgid "Message" |
||||
|
msgstr "Сообщение" |
||||
|
|
||||
|
#: models.py:90 |
||||
|
msgid "Date" |
||||
|
msgstr "Дата" |
||||
|
|
||||
|
#: models.py:91 |
||||
|
msgid "Sender" |
||||
|
msgstr "Отправитель" |
||||
|
|
||||
|
#: models.py:93 |
||||
|
msgid "Subscriber" |
||||
|
msgstr "Подписчик" |
||||
|
|
||||
|
#: models.py:100 |
||||
|
msgid "Viber message" |
||||
|
msgstr "Сообщение viber" |
||||
|
|
||||
|
#: models.py:101 |
||||
|
msgid "Viber messages" |
||||
|
msgstr "Сообщения viber" |
||||
|
|
||||
|
#: models.py:106 |
||||
|
msgid "User unique id in viber" |
||||
|
msgstr "Уникальный id viber" |
||||
|
|
||||
|
#: models.py:107 |
||||
|
msgid "Name" |
||||
|
msgstr "Имя" |
||||
|
|
||||
|
#: models.py:109 |
||||
|
msgid "System account" |
||||
|
msgstr "Системная учётная запись" |
||||
|
|
||||
|
#: models.py:116 |
||||
|
msgid "Viber subscriber" |
||||
|
msgstr "Подписчик viber" |
||||
|
|
||||
|
#: models.py:117 |
||||
|
msgid "Viber subscribers" |
||||
|
msgstr "Подписчики viber" |
||||
|
|
||||
|
#: templates/messenger/add_messenger.html:5 |
||||
|
msgid "Select bot type" |
||||
|
msgstr "Выберите тип бота" |
||||
|
|
||||
|
#: templates/messenger/add_messenger.html:11 |
||||
|
msgid "Add" |
||||
|
msgstr "Добавить" |
||||
|
|
||||
|
#: templates/messenger/messenger_list.html:21 |
||||
|
msgid "Type" |
||||
|
msgstr "Тип" |
||||
|
|
||||
|
#: templates/messenger/messenger_list.html:33 |
||||
|
msgid "Edit" |
||||
|
msgstr "Изменить" |
||||
|
|
||||
|
#: templates/messenger/messenger_list.html:40 |
||||
|
msgid "messengers was not found" |
||||
|
msgstr "Мэссенджеры не найдены" |
||||
|
|
||||
|
#: templates/messenger/messenger_list.html:49 |
||||
|
msgid "New" |
||||
|
msgstr "Новый" |
||||
|
|
||||
|
#: templates/messenger/vibermessenger_form.html:10 |
||||
|
msgid "Update messenger" |
||||
|
msgstr "Обновить мэссенджэр" |
||||
|
|
||||
|
#: templates/messenger/vibermessenger_form.html:11 |
||||
|
msgid "Change viber" |
||||
|
msgstr "Изменить viber" |
||||
|
|
||||
|
#: templates/messenger/vibermessenger_form.html:13 |
||||
|
msgid "Add messenger" |
||||
|
msgstr "Добавить мэссенджэр" |
||||
|
|
||||
|
#: templates/messenger/vibermessenger_form.html:14 |
||||
|
msgid "Add viber" |
||||
|
msgstr "Добавить viber" |
||||
|
|
||||
|
#: templates/messenger/vibermessenger_form.html:25 |
||||
|
msgid "Change messenger" |
||||
|
msgstr "Изменить мэссенджэр" |
||||
|
|
||||
|
#: templates/messenger/vibermessenger_form.html:28 |
||||
|
msgid "Add new messenger" |
||||
|
msgstr "Добавить мэссенджэр" |
||||
|
|
||||
|
#: templates/messenger/vibermessenger_form.html:39 |
||||
|
msgid "Save" |
||||
|
msgstr "Сохранить" |
||||
|
|
||||
|
#: templates/messenger/vibermessenger_form.html:43 |
||||
|
msgid "Send webhook" |
||||
|
msgstr "Отправить webhook" |
||||
|
|
||||
|
#: views.py:38 |
||||
|
msgid "Unexpected bot type" |
||||
|
msgstr "Не известный тип бота" |
||||
|
|
||||
|
#: views.py:51 |
||||
|
msgid "New viber messenger successfully created" |
||||
|
msgstr "Новый viber мэссенджэр успешно создан" |
||||
|
|
||||
|
#: views.py:62 |
||||
|
msgid "Viber messenger successfully updated" |
||||
|
msgstr "viber мэссенджэр успешно обновлён" |
||||
|
|
||||
|
#: views.py:73 |
||||
|
msgid "Viber messenger successfully deleted" |
||||
|
msgstr "viber мэссенджэр успешно удалён" |
||||
|
|
||||
|
#: views.py:132 |
||||
|
msgid "My telephone number" |
||||
|
msgstr "Мой номер телефона" |
||||
|
|
||||
|
#: views.py:150 |
||||
|
msgid "" |
||||
|
"Telephone not found, please specify telephone number in account in billing" |
||||
|
msgstr "" |
||||
|
"Номер телефона не найден. Укажите свой номер телефона в учётке в биллинге" |
||||
|
|
||||
|
msgid "Your account is attached. Now you will be receive notifications from billing" |
||||
|
msgstr "Ваша учётка из биллинга привязана. Теперь вы будете получать оповещения из биллинга." |
||||
@ -0,0 +1,88 @@ |
|||||
|
# Generated by Django 2.1.3 on 2019-02-07 12:31 |
||||
|
|
||||
|
from django.conf import settings |
||||
|
from django.db import migrations, models |
||||
|
import django.db.models.deletion |
||||
|
|
||||
|
|
||||
|
class Migration(migrations.Migration): |
||||
|
|
||||
|
initial = True |
||||
|
|
||||
|
dependencies = [ |
||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL), |
||||
|
] |
||||
|
|
||||
|
operations = [ |
||||
|
migrations.CreateModel( |
||||
|
name='Messenger', |
||||
|
fields=[ |
||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
||||
|
('title', models.CharField(max_length=64, verbose_name='Title')), |
||||
|
('bot_type', models.PositiveSmallIntegerField(blank=True, choices=[(1, 'Viber')], verbose_name='Bot type')), |
||||
|
('slug', models.SlugField(verbose_name='Slug')), |
||||
|
], |
||||
|
options={ |
||||
|
'verbose_name': 'messenger', |
||||
|
'verbose_name_plural': 'Messengers', |
||||
|
'db_table': 'messengers', |
||||
|
'ordering': ('title',), |
||||
|
}, |
||||
|
), |
||||
|
migrations.CreateModel( |
||||
|
name='ViberMessage', |
||||
|
fields=[ |
||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
||||
|
('msg', models.TextField(verbose_name='Message')), |
||||
|
('date', models.DateTimeField(auto_now_add=True, verbose_name='Date')), |
||||
|
('sender', models.CharField(max_length=32, verbose_name='Sender')), |
||||
|
], |
||||
|
options={ |
||||
|
'verbose_name': 'Viber message', |
||||
|
'verbose_name_plural': 'Viber messages', |
||||
|
'db_table': 'viber_messages_notifications', |
||||
|
'ordering': ('-date',), |
||||
|
}, |
||||
|
), |
||||
|
migrations.CreateModel( |
||||
|
name='ViberSubscriber', |
||||
|
fields=[ |
||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
||||
|
('uid', models.CharField(max_length=32, verbose_name='User unique id in viber')), |
||||
|
('name', models.CharField(blank=True, max_length=32, null=True, verbose_name='Name')), |
||||
|
('avatar', models.URLField(blank=True, max_length=250, null=True, verbose_name='Avatar')), |
||||
|
('account', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='System account')), |
||||
|
], |
||||
|
options={ |
||||
|
'verbose_name': 'Viber subscriber', |
||||
|
'verbose_name_plural': 'Viber subscribers', |
||||
|
'db_table': 'viber_subscriber', |
||||
|
'ordering': ('name',), |
||||
|
}, |
||||
|
), |
||||
|
migrations.CreateModel( |
||||
|
name='ViberMessenger', |
||||
|
fields=[ |
||||
|
('messenger_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='messenger.Messenger')), |
||||
|
('token', models.CharField(max_length=64, verbose_name='Bot secret token')), |
||||
|
('avatar', models.ImageField(null=True, upload_to='viber_avatar', verbose_name='Avatar')), |
||||
|
], |
||||
|
options={ |
||||
|
'verbose_name': 'Viber messenger', |
||||
|
'verbose_name_plural': 'Viber messengers', |
||||
|
'db_table': 'viber_messenger_notifications', |
||||
|
'ordering': ('title',), |
||||
|
}, |
||||
|
bases=('messenger.messenger',), |
||||
|
), |
||||
|
migrations.AddField( |
||||
|
model_name='vibermessage', |
||||
|
name='subscriber', |
||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='messenger.ViberSubscriber', verbose_name='Subscriber'), |
||||
|
), |
||||
|
migrations.AddField( |
||||
|
model_name='vibermessage', |
||||
|
name='messenger', |
||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='messenger.ViberMessenger', verbose_name='messenger'), |
||||
|
), |
||||
|
] |
||||
@ -0,0 +1,128 @@ |
|||||
|
from urllib.parse import urljoin |
||||
|
|
||||
|
from django.conf import settings |
||||
|
from django.shortcuts import resolve_url |
||||
|
from django.utils.translation import gettext_lazy as _ |
||||
|
from django.db import models |
||||
|
from viberbot import Api, BotConfiguration |
||||
|
from viberbot.api.messages import TextMessage |
||||
|
from viberbot.api.messages.message import Message |
||||
|
|
||||
|
from accounts_app.models import UserProfile |
||||
|
|
||||
|
|
||||
|
class Messenger(models.Model): |
||||
|
title = models.CharField(_('Title'), max_length=64) |
||||
|
CHAT_TYPES = ( |
||||
|
(1, _('Viber')), |
||||
|
) |
||||
|
bot_type = models.PositiveSmallIntegerField(_('Bot type'), choices=CHAT_TYPES, blank=True) |
||||
|
slug = models.SlugField(_('Slug')) |
||||
|
|
||||
|
def __str__(self): |
||||
|
return self.title |
||||
|
|
||||
|
class Meta: |
||||
|
db_table = 'messengers' |
||||
|
verbose_name = _('messenger') |
||||
|
verbose_name_plural = _('Messengers') |
||||
|
ordering = ('title',) |
||||
|
|
||||
|
def get_absolute_url(self): |
||||
|
if self.bot_type == 1: |
||||
|
return resolve_url('messenger:update_viber_messenger', self.slug) |
||||
|
|
||||
|
def get_next_url(self): |
||||
|
if self.bot_type == 1: # Viber |
||||
|
return resolve_url('messenger:update_viber_messenger', self.slug) |
||||
|
else: |
||||
|
return resolve_url('messenger:messengers_list') |
||||
|
|
||||
|
|
||||
|
class ViberMessenger(Messenger): |
||||
|
def __init__(self, *args, **kwargs): |
||||
|
super().__init__(*args, **kwargs) |
||||
|
self._viber_cache = None |
||||
|
|
||||
|
token = models.CharField(_('Bot secret token'), max_length=64) |
||||
|
avatar = models.ImageField(_('Avatar'), upload_to='viber_avatar', null=True) |
||||
|
|
||||
|
def get_viber(self): |
||||
|
if self._viber_cache is None: |
||||
|
self._viber_cache = Api(BotConfiguration( |
||||
|
name=str(self.slug), |
||||
|
avatar=self.avatar.url, |
||||
|
auth_token=str(self.token) |
||||
|
)) |
||||
|
return self._viber_cache |
||||
|
|
||||
|
def send_message(self, to: UserProfile, msg): |
||||
|
try: |
||||
|
viber = self.get_viber() |
||||
|
vs = to.vibersubscriber |
||||
|
if issubclass(msg.__class__, Message): |
||||
|
viber.send_messages(str(vs.uid), msg) |
||||
|
else: |
||||
|
viber.send_messages(str(vs.uid), TextMessage(text=msg)) |
||||
|
except ViberSubscriber.DoesNotExist: |
||||
|
pass |
||||
|
|
||||
|
def send_messages(self, receivers, msg_text: str): |
||||
|
""" |
||||
|
:param receivers: QuerySet of accounts_app.UserProfile |
||||
|
:param msg_text: text message |
||||
|
:return: nothing |
||||
|
""" |
||||
|
viber = self.get_viber() |
||||
|
msg = TextMessage(text=msg_text) |
||||
|
for vs in ViberSubscriber.objects.filter(account__in=receivers).iterator(): |
||||
|
viber.send_messages(str(vs.uid), msg) |
||||
|
|
||||
|
def send_webhook(self): |
||||
|
pub_url = getattr(settings, 'VIBER_BOT_PUBLIC_URL') |
||||
|
listen_url = resolve_url('messenger:listen_viber_bot', self.slug) |
||||
|
public_url = urljoin(pub_url, listen_url) |
||||
|
viber = self.get_viber() |
||||
|
viber.set_webhook(public_url, ['failed', 'subscribed', 'unsubscribed', 'conversation_started']) |
||||
|
|
||||
|
def __str__(self): |
||||
|
return self.title |
||||
|
|
||||
|
class Meta: |
||||
|
db_table = 'viber_messenger_notifications' |
||||
|
verbose_name = _('Viber messenger') |
||||
|
verbose_name_plural = _('Viber messengers') |
||||
|
ordering = ('title',) |
||||
|
|
||||
|
|
||||
|
class ViberMessage(models.Model): |
||||
|
msg = models.TextField(_('Message')) |
||||
|
date = models.DateTimeField(_('Date'), auto_now_add=True) |
||||
|
sender = models.CharField(_('Sender'), max_length=32) |
||||
|
messenger = models.ForeignKey(ViberMessenger, verbose_name=_('messenger'), on_delete=models.CASCADE) |
||||
|
subscriber = models.ForeignKey('ViberSubscriber', on_delete=models.SET_NULL, verbose_name=_('Subscriber'), null=True) |
||||
|
|
||||
|
def __str__(self): |
||||
|
return self.msg |
||||
|
|
||||
|
class Meta: |
||||
|
db_table = 'viber_messages_notifications' |
||||
|
verbose_name = _('Viber message') |
||||
|
verbose_name_plural = _('Viber messages') |
||||
|
ordering = ('-date',) |
||||
|
|
||||
|
|
||||
|
class ViberSubscriber(models.Model): |
||||
|
uid = models.CharField(_('User unique id in viber'), max_length=32) |
||||
|
name = models.CharField(_('Name'), max_length=32, null=True, blank=True) |
||||
|
avatar = models.URLField(_('Avatar'), max_length=250, null=True, blank=True) |
||||
|
account = models.OneToOneField(UserProfile, on_delete=models.CASCADE, verbose_name=_('System account'), blank=True, null=True) |
||||
|
|
||||
|
def __str__(self): |
||||
|
return self.name or 'no' |
||||
|
|
||||
|
class Meta: |
||||
|
db_table = 'viber_subscriber' |
||||
|
verbose_name = _('Viber subscriber') |
||||
|
verbose_name_plural = _('Viber subscribers') |
||||
|
ordering = ('name',) |
||||
@ -0,0 +1,56 @@ |
|||||
|
from typing import Optional, Iterable |
||||
|
|
||||
|
from celery import shared_task |
||||
|
|
||||
|
from accounts_app.models import UserProfile |
||||
|
from messenger.models import ViberMessenger |
||||
|
|
||||
|
|
||||
|
@shared_task |
||||
|
def send_viber_message(messenger_id: Optional[int], account_id: int, message_text: str) -> Optional[str]: |
||||
|
""" |
||||
|
Send text message via viber |
||||
|
:param messenger_id: Primary key UID for messanger.ViberMessenger |
||||
|
:param account_id: User id from accounts_app.UserProfile |
||||
|
:param message_text: |
||||
|
:return: Optional text for log |
||||
|
""" |
||||
|
if not message_text: |
||||
|
return 'ERROR: empty message text' |
||||
|
try: |
||||
|
sp = UserProfile.objects.get(pk=account_id) |
||||
|
if messenger_id is None: |
||||
|
for vm in ViberMessenger.objects.all().iterator(): |
||||
|
vm.send_message(sp, message_text) |
||||
|
else: |
||||
|
vm = ViberMessenger.objects.get(pk=messenger_id) |
||||
|
vm.send_message(sp, message_text) |
||||
|
except ViberMessenger.DoesNotExist: |
||||
|
return 'ERROR: Viber messanger with id=%d not found' % messenger_id |
||||
|
except UserProfile.DoesNotExist: |
||||
|
return 'ERROR: accounts_app.UserProfile with pk=%d does not exist' % account_id |
||||
|
|
||||
|
|
||||
|
@shared_task |
||||
|
def multicast_viber_notify(messenger_id: Optional[int], account_id_list: Iterable[int], message_text: str): |
||||
|
""" |
||||
|
Send multiple message via Viber to several addresses |
||||
|
:param messenger_id: Primary key UID for messanger.ViberMessenger |
||||
|
:param account_id_list: list of account ids from accounts_app.UserProfile |
||||
|
:param message_text: |
||||
|
:return: Optional text for log |
||||
|
""" |
||||
|
if not message_text: |
||||
|
return 'ERROR: empty message text' |
||||
|
account_id_list = tuple(account_id_list) |
||||
|
recipients = UserProfile.objects.filter(pk__in=account_id_list) |
||||
|
if not recipients.exists(): |
||||
|
return 'No recipients found from ids: %s' % ','.join(str(i) for i in account_id_list) |
||||
|
if messenger_id is None: |
||||
|
for vm in ViberMessenger.objects.all().iterator(): |
||||
|
vm.send_messages(recipients, message_text) |
||||
|
else: |
||||
|
vm = ViberMessenger.objects.filter(pk=messenger_id).first() |
||||
|
if vm is None: |
||||
|
return 'ERROR ViberMessenger with pk=%d does not exist' % messenger_id |
||||
|
vm.send_messages(recipients, message_text) |
||||
@ -0,0 +1,15 @@ |
|||||
|
{% load i18n bootstrap3 %} |
||||
|
<form role="form" action="{% url 'messenger:add_messenger' %}" method="post">{% csrf_token %} |
||||
|
<div class="modal-header primary"> |
||||
|
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> |
||||
|
<h4 class="modal-title"><span class="glyphicon glyphicon-blackboard"></span>{% trans 'Select bot type' %}</h4> |
||||
|
</div> |
||||
|
|
||||
|
<div class="modal-body"> |
||||
|
{% bootstrap_form form %} |
||||
|
<button type="submit" class="btn btn-success"> |
||||
|
<span class="glyphicon glyphicon-plus"></span> {% trans 'Add' %} |
||||
|
</button> |
||||
|
</div> |
||||
|
|
||||
|
</form> |
||||
@ -0,0 +1,57 @@ |
|||||
|
{% extends 'base.html' %} |
||||
|
{% load dpagination i18n %} |
||||
|
|
||||
|
{% block breadcrumb %} |
||||
|
<ol class="breadcrumb"> |
||||
|
<li><span class="glyphicon glyphicon-home"></span></li> |
||||
|
<li class="active">{% trans 'Messengers' %}</li> |
||||
|
</ol> |
||||
|
{% endblock %} |
||||
|
|
||||
|
{% block page-header %} |
||||
|
{% trans 'Messengers' %} |
||||
|
{% endblock %} |
||||
|
|
||||
|
{% block main %} |
||||
|
<div class="table-responsive"> |
||||
|
<table class="table table-striped table-bordered"> |
||||
|
<thead> |
||||
|
<tr> |
||||
|
<th class="col-sm-4">{% trans 'Title' %}</th> |
||||
|
<th class="col-sm-3">{% trans 'Type' %}</th> |
||||
|
<th class="col-sm-4">{% trans 'Slug' %}</th> |
||||
|
<th class="col-sm-1">#</th> |
||||
|
</tr> |
||||
|
</thead> |
||||
|
<tbody> |
||||
|
{% for messenger in object_list %} |
||||
|
<tr> |
||||
|
<td>{{ messenger.title }}</td> |
||||
|
<td>{{ messenger.get_bot_type_display }}</td> |
||||
|
<td>{{ messenger.slug }}</td> |
||||
|
<td> |
||||
|
<a href="{{ messenger.get_absolute_url }}" class="btn btn-sm btn-default" title="{% trans 'Edit' %}"> |
||||
|
<span class="glyphicon glyphicon-edit"></span> |
||||
|
</a> |
||||
|
</td> |
||||
|
</tr> |
||||
|
{% empty %} |
||||
|
<tr> |
||||
|
<td colspan="4"><a href="#">{% trans 'messengers was not found' %}</a></td> |
||||
|
</tr> |
||||
|
{% endfor %} |
||||
|
</tbody> |
||||
|
{% if perms.messenger.add_messenger %} |
||||
|
<tfoot> |
||||
|
<tr> |
||||
|
<td colspan="4" class="btn-group btn-group-sm"> |
||||
|
<a href="{% url 'messenger:add_messenger' %}" class="btn btn-default btn-modal"> |
||||
|
<span class="glyphicon glyphicon-plus"></span> <span class="hidden-xs">{% trans 'New' %}</span> |
||||
|
</a> |
||||
|
</td> |
||||
|
</tr> |
||||
|
</tfoot> |
||||
|
{% endif %} |
||||
|
</table> |
||||
|
</div> |
||||
|
{% endblock %} |
||||
@ -0,0 +1,49 @@ |
|||||
|
{% extends request.is_ajax|yesno:'bajax.html,base.html' %} |
||||
|
{% load i18n bootstrap3 %} |
||||
|
|
||||
|
|
||||
|
{% block breadcrumb %} |
||||
|
<ol class="breadcrumb"> |
||||
|
<li><span class="glyphicon glyphicon-home"></span></li> |
||||
|
<li><a href="{% url 'messenger:messengers_list' %}">{% trans 'Messengers' %}</a></li> |
||||
|
{% if object %} |
||||
|
<li><a href="{{ object.get_absolute_url }}">{% trans 'Update messenger' %}</a></li> |
||||
|
<li class="active">{% trans 'Change viber' %}</li> |
||||
|
{% else %} |
||||
|
<li><a href="{% url 'messenger:add_messenger' %}">{% trans 'Add messenger' %}</a></li> |
||||
|
<li class="active">{% trans 'Add viber' %}</li> |
||||
|
{% endif %} |
||||
|
|
||||
|
</ol> |
||||
|
{% endblock %} |
||||
|
|
||||
|
|
||||
|
{% block main %} |
||||
|
|
||||
|
{% if object %} |
||||
|
{% url 'messenger:update_viber_messenger' object.slug as form_url %} |
||||
|
{% trans 'Change messenger' as panel_title %} |
||||
|
{% else %} |
||||
|
{% url 'messenger:add_viber_messenger' as form_url %} |
||||
|
{% trans 'Add new messenger' as panel_title %} |
||||
|
{% endif %} |
||||
|
|
||||
|
<div class="panel panel-default"> |
||||
|
<div class="panel-heading"> |
||||
|
<h3 class="panel-title">{{ panel_title }}</h3> |
||||
|
</div> |
||||
|
<div class="panel-body"> |
||||
|
<form role="form" action="{{ form_url }}" method="post" enctype="multipart/form-data">{% csrf_token %} |
||||
|
{% bootstrap_form form %} |
||||
|
<button type="submit" class="btn btn-sm btn-default"> |
||||
|
<span class="glyphicon glyphicon-save"></span> {% trans 'Save' %} |
||||
|
</button> |
||||
|
{% if object %} |
||||
|
<a href="{% url 'messenger:webhook_viber_bot' object.slug %}" class="btn btn-default btn-sm btn-modal"> |
||||
|
<span class="glyphicon glyphicon-share"></span> {% trans 'Send webhook' %} |
||||
|
</a> |
||||
|
{% endif %} |
||||
|
</form> |
||||
|
</div> |
||||
|
</div> |
||||
|
{% endblock %} |
||||
@ -0,0 +1,3 @@ |
|||||
|
from django.test import TestCase |
||||
|
|
||||
|
# Create your tests here. |
||||
@ -0,0 +1,17 @@ |
|||||
|
from django.urls import path |
||||
|
from django.views.decorators.csrf import csrf_exempt |
||||
|
|
||||
|
from messenger import views |
||||
|
|
||||
|
|
||||
|
app_name = 'messenger' |
||||
|
|
||||
|
urlpatterns = [ |
||||
|
path('', views.messengerListView.as_view(), name='messengers_list'), |
||||
|
path('new/', views.AddmessengerCreateView.as_view(), name='add_messenger'), |
||||
|
path('viber/new/', views.AddmessengerViberCreateView.as_view(), name='add_viber_messenger'), |
||||
|
path('viber/<slug:slug>/update/', views.UpdateVibermessengerUpdateView.as_view(), name='update_viber_messenger'), |
||||
|
path('viber/<slug:slug>/delete/', views.RemoveVibermessengerDeleteView.as_view(), name='delete_viber_messenger'), |
||||
|
path('viber/<slug:slug>/listen/', csrf_exempt(views.ListenViberView.as_view()), name='listen_viber_bot'), |
||||
|
path('viber/<slug:slug>/set_webhook/', views.SetWebhook.as_view(), name='webhook_viber_bot'), |
||||
|
] |
||||
@ -0,0 +1,161 @@ |
|||||
|
from django.contrib import messages |
||||
|
from django.contrib.auth.mixins import PermissionRequiredMixin |
||||
|
from django.http import HttpResponseForbidden, HttpResponse, HttpResponseNotFound |
||||
|
from django.shortcuts import resolve_url |
||||
|
from django.urls import reverse_lazy |
||||
|
from django.utils.decorators import method_decorator |
||||
|
from django.utils.translation import gettext_lazy as _, gettext |
||||
|
from django.views.decorators.csrf import csrf_exempt |
||||
|
from django.views.generic import ListView, CreateView, UpdateView, DeleteView, FormView, View |
||||
|
from django.views.generic.detail import SingleObjectMixin |
||||
|
from viberbot.api.messages import KeyboardMessage, ContactMessage |
||||
|
from viberbot.api.user_profile import UserProfile as ViberUserProfile |
||||
|
from viberbot.api.viber_requests import ViberMessageRequest, ViberSubscribedRequest, ViberFailedRequest, \ |
||||
|
ViberUnsubscribedRequest |
||||
|
|
||||
|
from accounts_app.models import UserProfile |
||||
|
from djing.lib.mixins import LoginAdminPermissionMixin, LoginAdminMixin |
||||
|
from messenger import forms, models |
||||
|
|
||||
|
from messenger.models import ViberMessage, ViberSubscriber |
||||
|
|
||||
|
|
||||
|
class messengerListView(LoginAdminPermissionMixin, ListView): |
||||
|
model = models.Messenger |
||||
|
permission_required = 'messenger.view_messenger' |
||||
|
|
||||
|
|
||||
|
class AddmessengerCreateView(LoginAdminMixin, FormView): |
||||
|
template_name = 'messenger/add_messenger.html' |
||||
|
form_class = forms.MessengerForm |
||||
|
|
||||
|
def form_valid(self, form): |
||||
|
bot_type = form.cleaned_data.get('bot_type') |
||||
|
if isinstance(bot_type, int) and bot_type > 0: |
||||
|
if bot_type == 1: |
||||
|
self.success_url = resolve_url('messenger:add_viber_messenger') |
||||
|
return super().form_valid(form) |
||||
|
messages.info(self.request, _('Unexpected bot type')) |
||||
|
self.success_url = resolve_url('messenger:messengers_list') |
||||
|
return super().form_valid(form) |
||||
|
|
||||
|
|
||||
|
class AddmessengerViberCreateView(LoginAdminMixin, PermissionRequiredMixin, CreateView): |
||||
|
model = models.ViberMessenger |
||||
|
form_class = forms.MessengerViberForm |
||||
|
permission_required = 'messenger.add_vibermessenger' |
||||
|
success_url = reverse_lazy('messenger:messengers_list') |
||||
|
|
||||
|
def form_valid(self, form): |
||||
|
r = super().form_valid(form) |
||||
|
messages.success(self.request, _('New viber messenger successfully created')) |
||||
|
return r |
||||
|
|
||||
|
|
||||
|
class UpdateVibermessengerUpdateView(LoginAdminPermissionMixin, UpdateView): |
||||
|
model = models.ViberMessenger |
||||
|
form_class = forms.MessengerViberForm |
||||
|
permission_required = 'messenger.change_vibermessenger' |
||||
|
|
||||
|
def form_valid(self, form): |
||||
|
r = super().form_valid(form) |
||||
|
messages.success(self.request, _('Viber messenger successfully updated')) |
||||
|
return r |
||||
|
|
||||
|
|
||||
|
class RemoveVibermessengerDeleteView(LoginAdminPermissionMixin, DeleteView): |
||||
|
model = models.ViberMessenger |
||||
|
permission_required = 'messenger.delete_vibermessenger' |
||||
|
success_url = reverse_lazy('messenger:messengers_list') |
||||
|
|
||||
|
def delete(self, request, *args, **kwargs): |
||||
|
r = super().delete(request, *args, **kwargs) |
||||
|
messages.success(request, _('Viber messenger successfully deleted')) |
||||
|
return r |
||||
|
|
||||
|
|
||||
|
@method_decorator(csrf_exempt, name='post') |
||||
|
class ListenViberView(SingleObjectMixin, View): |
||||
|
http_method_names = 'post', |
||||
|
model = models.ViberMessenger |
||||
|
|
||||
|
def post(self, request, *args, **kwargs): |
||||
|
obj = self.get_object() |
||||
|
if not obj: |
||||
|
return HttpResponseNotFound() |
||||
|
self.object = obj |
||||
|
viber = obj.get_viber() |
||||
|
if not viber.verify_signature(request.body, request.META.get('HTTP_X_VIBER_CONTENT_SIGNATURE')): |
||||
|
return HttpResponseForbidden() |
||||
|
# this library supplies a simple way to receive a request object |
||||
|
vr = viber.parse_request(request.body) |
||||
|
if isinstance(vr, ViberMessageRequest): |
||||
|
in_msg = vr.message |
||||
|
if isinstance(in_msg, ContactMessage): |
||||
|
self.inbox_contact(in_msg, vr.sender) |
||||
|
subscriber, created = self.make_subscriber(vr.sender) |
||||
|
if not created: |
||||
|
ViberMessage.objects.create( |
||||
|
msg=vr.message, |
||||
|
sender=vr.sender.id, |
||||
|
messenger=obj, |
||||
|
subscriber=subscriber |
||||
|
) |
||||
|
elif isinstance(vr, ViberSubscribedRequest): |
||||
|
self.make_subscriber(vr.user) |
||||
|
elif isinstance(vr, ViberFailedRequest): |
||||
|
print("client failed receiving message. failure: {0}".format(vr)) |
||||
|
elif isinstance(vr, ViberUnsubscribedRequest): |
||||
|
ViberSubscriber.objects.filter( |
||||
|
uid=vr.user_id |
||||
|
).delete() |
||||
|
return HttpResponse(status=200) |
||||
|
|
||||
|
def make_subscriber(self, viber_user_profile: ViberUserProfile): |
||||
|
subscriber, created = ViberSubscriber.objects.get_or_create( |
||||
|
uid=viber_user_profile.id, |
||||
|
defaults={ |
||||
|
'name': viber_user_profile.name, |
||||
|
'avatar': viber_user_profile.avatar |
||||
|
} |
||||
|
) |
||||
|
if created and hasattr(self, 'object'): |
||||
|
msg = KeyboardMessage(keyboard={ |
||||
|
'Type': 'keyboard', |
||||
|
'DefaultHeight': True, |
||||
|
'Buttons': ({ |
||||
|
'ActionType': 'share-phone', |
||||
|
'ActionBody': 'reply to me', |
||||
|
"Text": gettext('My telephone number'), |
||||
|
"TextSize": "medium" |
||||
|
},) |
||||
|
}, min_api_version=3) |
||||
|
viber = self.object.get_viber() |
||||
|
viber.send_messages(viber_user_profile.id, msg) |
||||
|
return subscriber, created |
||||
|
|
||||
|
def inbox_contact(self, msg, sender: ViberUserProfile): |
||||
|
tel = msg.contact.phone_number |
||||
|
accs = UserProfile.objects.filter(telephone__icontains=tel) |
||||
|
viber = self.object.get_viber() |
||||
|
if accs.exists(): |
||||
|
subs = ViberSubscriber.objects.filter(uid=sender.id) |
||||
|
if subs.exists(): |
||||
|
subs.update(account=accs.first()) |
||||
|
viber.send_messages(sender.id, gettext( |
||||
|
'Your account is attached. Now you will be receive notifications from billing' |
||||
|
)) |
||||
|
else: |
||||
|
viber.send_messages(sender.id, gettext('Telephone not found, please specify telephone number in account in billing')) |
||||
|
|
||||
|
|
||||
|
class SetWebhook(LoginAdminMixin, SingleObjectMixin, View): |
||||
|
http_method_names = 'get', |
||||
|
model = models.ViberMessenger |
||||
|
|
||||
|
def get(self, request, *args, **kwargs): |
||||
|
obj = self.get_object() |
||||
|
if not obj: |
||||
|
return HttpResponseNotFound |
||||
|
obj.send_webhook() |
||||
|
return HttpResponse(b'ok', status=200) |
||||
@ -1,11 +1,18 @@ |
|||||
<!DOCTYPE html> |
<!DOCTYPE html> |
||||
<html> |
|
||||
|
<html lang="ru"> |
||||
<head> |
<head> |
||||
<title>Старый браузер</title> |
|
||||
<meta charset="UTF-8"> |
|
||||
|
<title>Старый браузер</title> |
||||
|
<meta charset="UTF-8"> |
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
||||
</head> |
</head> |
||||
<body> |
<body> |
||||
<h1>У вас старый ослик, обновитесь хотяб до IE10</h1> |
|
||||
|
<h1>Ваш InternetExplorer устарел, обновите ваш браузер на более современный</h1> |
||||
|
<p>Можете воспользоваться ссылками ниже:</p> |
||||
|
<ul> |
||||
|
<li><a href="https://www.google.ru/chrome/">Google Chrome</a></li> |
||||
|
<li><a href="https://www.opera.com/ru/download">Opera</a></li> |
||||
|
<li><a href="https://browser.yandex.ru/">Yandex Browser</a></li> |
||||
|
<li><a href="https://www.mozilla.org/ru/firefox/new/">Mozilla Firefox</a></li> |
||||
|
</ul> |
||||
</body> |
</body> |
||||
</html> |
</html> |
||||
4
static/css/all.min.css
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
Some files were not shown because too many files changed in this diff
Write
Preview
Loading…
Cancel
Save
Reference in new issue