diff --git a/abonapp/locale/ru/LC_MESSAGES/django.po b/abonapp/locale/ru/LC_MESSAGES/django.po index 27b7a3f..5bb4ba3 100644 --- a/abonapp/locale/ru/LC_MESSAGES/django.po +++ b/abonapp/locale/ru/LC_MESSAGES/django.po @@ -1138,3 +1138,6 @@ msgstr "Абоненты" msgid "Successfully saved" msgstr "Успешно сохранено" + +msgid "This user can not buy admin services" +msgstr "Этот пользователь не может назначать административные услуги" diff --git a/abonapp/models.py b/abonapp/models.py index 462f45e..43a9331 100644 --- a/abonapp/models.py +++ b/abonapp/models.py @@ -191,7 +191,7 @@ class Abon(BaseAccount): AbonLog.objects.create( abon=self, amount=amount, - author=current_user, + author=current_user if isinstance(current_user, UserProfile) else None, comment=comment ) self.ballance += amount @@ -202,8 +202,11 @@ class Abon(BaseAccount): amount = round(tariff.amount, 2) - if not author.is_staff and tariff.is_admin: - raise LogicError(_('User that is no staff can not buy admin services')) + if tariff.is_admin: + if author is not None and not author.is_staff: + raise LogicError(_('User that is no staff can not buy admin services')) + else: + raise LogicError(_('This user can not buy admin services')) if self.current_tariff is not None: if self.current_tariff.tariff == tariff: @@ -347,7 +350,6 @@ class AllTimePayLogManager(models.Manager): r = cur.fetchone() if r is None: break summ, dat = r - print(summ, dat) yield {'summ': summ, 'pay_date': datetime.strptime(dat, '%Y-%m-%d')} diff --git a/abonapp/pay_systems.py b/abonapp/pay_systems.py index b5b4b62..378609b 100644 --- a/abonapp/pay_systems.py +++ b/abonapp/pay_systems.py @@ -67,8 +67,7 @@ def allpay(request): if pays.count() > 0: return bad_ret(-100) - # тут в author передаём учётку абонента, т.к. это он сам через терминал пополняет - abon.add_ballance(abon, pay_amount, comment='AllPay %.2f' % pay_amount) + abon.add_ballance(None, pay_amount, comment='AllPay %.2f' % pay_amount) abon.save(update_fields=['ballance']) AllTimePayLog.objects.create( diff --git a/abonapp/templates/abonapp/log.html b/abonapp/templates/abonapp/log.html index e8ef0e9..d180fc9 100644 --- a/abonapp/templates/abonapp/log.html +++ b/abonapp/templates/abonapp/log.html @@ -39,7 +39,12 @@ {{ l.comment }} {{ l.date|date:"D d E Y H:i:s" }} - {{ l.author.username }} + + {% if l.author %} + {{ l.author.username }} + {% else %} + --- + {% endif %} {% empty %} diff --git a/abonapp/templates/abonapp/peoples.html b/abonapp/templates/abonapp/peoples.html index c2854f7..f22e2e9 100644 --- a/abonapp/templates/abonapp/peoples.html +++ b/abonapp/templates/abonapp/peoples.html @@ -63,7 +63,7 @@ - {% with can_ch_trf=perms.tariff_app.change_tariff can_del_trf=perms.abonapp.delete_abon %} + {% with can_ch_trf=perms.tariff_app.change_tariff can_del_abon=perms.abonapp.delete_abon %} {% for human in peoples %} {% if human.is_active %} @@ -94,11 +94,11 @@ {{ human.house|default:'-' }} {{ human.telephone }} - {% if human.active_tariff %} - {% if perms.tariff_app.change_tariff %} - {{ human.active_tariff.tariff.title }} + {% if human.current_tariff %} + {% if can_ch_trf %} + {{ human.current_tariff.tariff.title }} {% else %} - {{ human.active_tariff.tariff.title }} + {{ human.current_tariff.tariff.title }} {% endif %} {% else %}——— {% endif %} @@ -109,7 +109,7 @@ {% endfor %} - {% if can_del_trf %} + {% if can_del_abon %} diff --git a/abonapp/views.py b/abonapp/views.py index 4f5a258..35792d0 100644 --- a/abonapp/views.py +++ b/abonapp/views.py @@ -6,7 +6,7 @@ from django.db import IntegrityError, ProgrammingError, transaction from django.db.models import Count, Q from django.shortcuts import render, redirect, get_object_or_404, resolve_url from django.contrib.auth.decorators import login_required -from django.http import HttpResponse, HttpResponseBadRequest +from django.http import HttpResponse, Http404 from django.contrib import messages from django.utils.translation import gettext_lazy as _ from django.utils.decorators import method_decorator @@ -27,17 +27,17 @@ from statistics.models import getModel from group_app.models import Group from guardian.shortcuts import get_objects_for_user, assign_perm from guardian.decorators import permission_required_or_403 as permission_required -from djing.global_base_views import OrderingMixin +from djing.global_base_views import OrderingMixin, BaseListWithFiltering PAGINATION_ITEMS_PER_PAGE = getattr(settings, 'PAGINATION_ITEMS_PER_PAGE', 10) -@method_decorator([login_required, mydefs.only_admins], name='dispatch') -class BaseAbonListView(ListView, OrderingMixin): +class BaseAbonListView(OrderingMixin, BaseListWithFiltering): paginate_by = PAGINATION_ITEMS_PER_PAGE http_method_names = ['get'] +@method_decorator([login_required, mydefs.only_admins], name='dispatch') class PeoplesListView(BaseAbonListView): context_object_name = 'peoples' template_name = 'abonapp/peoples.html' @@ -45,7 +45,7 @@ class PeoplesListView(BaseAbonListView): def get_queryset(self): street_id = mydefs.safe_int(self.request.GET.get('street')) gid = mydefs.safe_int(self.kwargs.get('gid')) - peoples_list = models.Abon.objects.select_related('group', 'street') + peoples_list = models.Abon.objects.all().select_related('group', 'street', 'current_tariff') if street_id > 0: peoples_list = peoples_list.filter(group__pk=gid, street=street_id) else: @@ -60,7 +60,11 @@ class PeoplesListView(BaseAbonListView): pass except mydefs.LogicError as e: messages.warning(self.request, e) - + ordering = self.get_ordering() + if ordering: + if isinstance(ordering, str): + ordering = (ordering,) + peoples_list = peoples_list.order_by(*ordering) return peoples_list def get_context_data(self, **kwargs): @@ -79,6 +83,7 @@ class PeoplesListView(BaseAbonListView): return context +@method_decorator([login_required, mydefs.only_admins], name='dispatch') class GroupListView(BaseAbonListView): context_object_name = 'groups' template_name = 'abonapp/group_list.html' @@ -186,6 +191,7 @@ def abonamount(request, gid, uid): }, request=request) +@method_decorator([login_required, mydefs.only_admins], name='dispatch') @method_decorator(permission_required('group_app.can_view_group', (Group, 'pk', 'gid')), name='dispatch') class DebtsListView(BaseAbonListView): context_object_name = 'invoices' @@ -203,6 +209,7 @@ class DebtsListView(BaseAbonListView): return context +@method_decorator([login_required, mydefs.only_admins], name='dispatch') @method_decorator(permission_required('group_app.can_view_group', (Group, 'pk', 'gid')), name='dispatch') class PayHistoryListView(BaseAbonListView): context_object_name = 'pay_history' @@ -211,7 +218,7 @@ class PayHistoryListView(BaseAbonListView): def get_queryset(self): abon = get_object_or_404(models.Abon, pk=self.kwargs.get('uid')) self.abon = abon - pay_history = models.AbonLog.objects.filter(abon=abon).order_by('-id') + pay_history = models.AbonLog.objects.filter(abon=abon).order_by('-date') return pay_history def get_context_data(self, **kwargs): @@ -402,8 +409,8 @@ def unsubscribe_service(request, gid, uid, abon_tariff_id): try: abon = get_object_or_404(models.Abon, pk=uid) abon_tariff = get_object_or_404(models.AbonTariff, pk=int(abon_tariff_id)) - abon_tariff.delete() abon.sync_with_nas(created=False) + abon_tariff.delete() messages.success(request, _('User has been detached from service')) except NasFailedResult as e: messages.error(request, e) @@ -545,7 +552,8 @@ def clear_dev(request, gid, uid): try: abon = models.Abon.objects.get(pk=uid) abon.device = None - abon.save(update_fields=['device']) + abon.dev_port = None + abon.save(update_fields=['device', 'dev_port']) messages.success(request, _('Device has successfully unattached')) except models.Abon.DoesNotExist: messages.error(request, _('Abon does not exist')) @@ -710,6 +718,7 @@ def abon_ping(request): })) +@method_decorator([login_required, mydefs.only_admins], name='dispatch') class DialsListView(BaseAbonListView): context_object_name = 'logs' template_name = 'abonapp/dial_log.html' diff --git a/accounts_app/views.py b/accounts_app/views.py index 19d6fa7..6e72be1 100644 --- a/accounts_app/views.py +++ b/accounts_app/views.py @@ -213,7 +213,7 @@ def perms(request, uid): klasses = ( 'abonapp.Abon', 'accounts_app.UserProfile', 'abonapp.AbonTariff', 'abonapp.AbonStreet', 'devapp.Device', - 'abonapp.PassportInfo', 'abonapp.AdditionalTelephone' + 'abonapp.PassportInfo', 'abonapp.AdditionalTelephone', 'tariff_app.PeriodicPay' ) return render(request, 'accounts/perms/objects_types.html', { 'userprofile': userprofile, diff --git a/agent/netflow/netflow_handler.py b/agent/netflow/netflow_handler.py index f88a4ea..e3f84bd 100755 --- a/agent/netflow/netflow_handler.py +++ b/agent/netflow/netflow_handler.py @@ -4,6 +4,7 @@ import sys import os from importlib import import_module + if __name__ == '__main__': if len(sys.argv) < 2: print("File name of netflow required") @@ -35,7 +36,7 @@ if __name__ == '__main__': sql = r'SELECT abonent.ip_address, acc.username ' \ r'FROM abonent ' \ - r'LEFT JOIN accounts_app_userprofile AS acc ON (acc.id = abonent.userprofile_ptr_id) ' \ + r'LEFT JOIN base_accounts AS acc ON (acc.id = abonent.baseaccount_ptr_id) ' \ r'WHERE abonent.ip_address != 0' ln = cursor.execute(sql) with open(tmp_ipuser_file, 'w') as f: @@ -48,8 +49,8 @@ if __name__ == '__main__': db.close() os.system( - '/usr/bin/bash -c ' - '"%(CUR_DIR)s/djing_flow %(TMP_IPUSER_FILE)s < %(TMP_DUMP)s | ' + 'bash -c "export LD_LIBRARY_PATH=. && ' + '%(CUR_DIR)s/djing_flow %(TMP_IPUSER_FILE)s < %(TMP_DUMP)s | ' '/usr/bin/mysql -u%(DB_USER)s -h %(HOST)s -p %(DB_NAME)s --password=%(DB_PASSW)s"' % { 'CUR_DIR': cur_dir, 'TMP_IPUSER_FILE': tmp_ipuser_file, diff --git a/chatbot/models.py b/chatbot/models.py index fffe6f1..b10f2bd 100644 --- a/chatbot/models.py +++ b/chatbot/models.py @@ -57,7 +57,7 @@ class MessageQueue(models.Model): ('r', 'Read') ) status = models.CharField(_('Status of message'), max_length=1, choices=STATUSES, default='n') - # tag каждое приложение ставит своим чтоб делить сообщения между этими приложениями + # tag: each application puts its own to separate messages between these applications tag = models.CharField(_('App tag'), max_length=6, default='none') objects = MessageQueueManager() diff --git a/clientsideapp/locale/ru/LC_MESSAGES/django.po b/clientsideapp/locale/ru/LC_MESSAGES/django.po index 79077a7..b72ec63 100644 --- a/clientsideapp/locale/ru/LC_MESSAGES/django.po +++ b/clientsideapp/locale/ru/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-03-08 14:24+0300\n" +"POT-Creation-Date: 2018-03-20 01:51+0300\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: Dmitry Novikov nerosketch@gmail.com\n" "Language: ru\n" @@ -32,34 +32,113 @@ msgid "Are you sure you want to spend a payment?" msgstr "Вы уверены что хотите провести платёж?" #: templates/clientsideapp/debt_buy.html:21 -#, fuzzy, python-format -#| msgid "" -#| "From your account, they withdraw funds in %(amount)s rub.
\n" -#| "As a result, you will remain on your account %(ballance_after)s rubles. " -#| "
\n" -#| "The administrator can immediately see that you shut down the debt." +#, python-format msgid "" -"From your account, they withdraw funds in %(amount)s rub.
\n" -"As a result, you will remain on your account %(ballance_after)s rubles.
\n" -"The administrator can immediately see that you shut down the debt." +"From your account, they withdraw funds in %(amount)s rub.
As a " +"result, you will remain on your account %(ballance_after)s rubles.
The " +"administrator can immediately see that you shut down the debt." msgstr "" -"С вашего счёта снимутся средства в размере %(amount)s руб.
\n" -"В результате у вас на счету останется %(ballance_after)s руб.
\n" -"Администратор сразу сможет видеть что у вас закрыта задолженность." +"С вашего счёта снимутся средства в размере %(amount)s руб.
В " +"результате у вас на счету останется %(ballance_after)s руб.
Администратор сразу сможет видеть что у вас закрыта задолженность." -#: templates/clientsideapp/debt_buy.html:24 +#: templates/clientsideapp/debt_buy.html:26 msgid "Description of payment" msgstr "Описание платежа" -#: templates/clientsideapp/debt_buy.html:32 +#: templates/clientsideapp/debt_buy.html:34 msgid "Confirm" msgstr "Подтвердить" -#: templates/clientsideapp/debt_buy.html:35 +#: templates/clientsideapp/debt_buy.html:37 msgid "Cancel" msgstr "Отменить" +#: templates/clientsideapp/debts.html:6 +msgid "Your debt" +msgstr "Ваши долги" + +#: templates/clientsideapp/debts.html:11 +msgid "State" +msgstr "Состояние" + +#: templates/clientsideapp/debts.html:12 +msgid "Summ" +msgstr "Сумма" + +#: templates/clientsideapp/debts.html:13 +msgid "Description" +msgstr "Описание" + +#: templates/clientsideapp/debts.html:14 +msgid "Date of create" +msgstr "Дата создания" + +#: templates/clientsideapp/debts.html:15 +msgid "Date of pay" +msgstr "Дата платежа" + +#: templates/clientsideapp/debts.html:16 templates/clientsideapp/debts.html:38 +msgid "Pay" +msgstr "Оплатить" + +#: templates/clientsideapp/debts.html:23 templates/clientsideapp/ext.html:63 +#: templates/clientsideapp/modal_service_buy.html:19 +#: templates/clientsideapp/services.html:26 +#: templates/clientsideapp/services.html:55 +msgid "currency" +msgstr "руб." + +#: templates/clientsideapp/debts.html:31 +msgid "Created paid" +msgstr "Создан оплаченным" + +#: templates/clientsideapp/debts.html:33 +msgid "Not yet paid" +msgstr "Ещё не оплачен" + +#: templates/clientsideapp/debts.html:45 +msgid "You have no debt" +msgstr "У вас нет долгов" + +#: templates/clientsideapp/ext.html:7 templates/clientsideapp/ext.html:41 +msgid "Personal account" +msgstr "Личный кабинет" + +#: templates/clientsideapp/ext.html:46 +msgid "Pays" +msgstr "Платежи" + +#: templates/clientsideapp/ext.html:51 +msgid "Services" +msgstr "Услуги" + +#: templates/clientsideapp/ext.html:55 +msgid "Other" +msgstr "Другое" + +#: templates/clientsideapp/ext.html:57 +msgid "Show debts and pay it" +msgstr "Посмотреть долги и оплатить" + +#: templates/clientsideapp/ext.html:58 +msgid "Quit" +msgstr "Выйти" + +#: templates/clientsideapp/ext.html:63 +#, python-format +msgid "Your balance is %(ballance)s" +msgstr "Ваш балланс %(ballance)s" + +#: templates/clientsideapp/ext.html:74 +msgid "" +"Attantion! You are is admin, and do not be active here, " +"please back to admin side. Client side to you for reference only." +msgstr "" +"Кстати. Вы администратор, и не должны производить действия " +"из кабинета пользователя, производите их из админки. Кабинет клиента для вас " +"только для ознакомления." + #: templates/clientsideapp/modal_service_buy.html:5 msgid "Pick service" msgstr "Заказать услугу" @@ -68,20 +147,32 @@ msgstr "Заказать услугу" msgid "Are you sure you want to order the service?" msgstr "Вы уверены что хотите заказать услугу?" +#: templates/clientsideapp/modal_service_buy.html:9 +msgid "" +"Be careful, after purchasing the service you will withdraw money, and " +"you will be able to use the purchased service." +msgstr "" +"Будте внимательны, после заказа услуги с вашего счёта снимутся средства, и вы сможете пользоваться купленной услугой." + #: templates/clientsideapp/modal_service_buy.html:15 #, python-format msgid "" -"Inbound speed: %(speedIn)s MBit/s
\n" -"Outgoing speed: %(speedOut)s MBit/s
\n" -"Cost: %(amount)s rubles." +"Inbound speed: %(speedIn)s MBit/s
Outgoing speed: %(speedOut)s MBit/" +"s
Cost: %(amount)s rubles." msgstr "" -#: templates/clientsideapp/modal_service_buy.html:22 -#: templates/clientsideapp/services.html:59 +#: templates/clientsideapp/modal_service_buy.html:19 +#, python-format +msgid "The cost is %(amount)s" +msgstr "Стоимость %(amount)s" + +#: templates/clientsideapp/modal_service_buy.html:23 +#: templates/clientsideapp/services.html:63 msgid "Pick" msgstr "Заказать" -#: templates/clientsideapp/modal_service_buy.html:24 +#: templates/clientsideapp/modal_service_buy.html:25 msgid "Close" msgstr "Закрыть" @@ -105,9 +196,37 @@ msgstr "Комментарий" msgid "You have not spent payments" msgstr "У вас нет проведённых платежей" +#: templates/clientsideapp/services.html:14 +msgid "Your current service" +msgstr "Ваша текущая услуга" + +#: templates/clientsideapp/services.html:19 +msgid "The date of connection" +msgstr "Дата подключения" + +#: templates/clientsideapp/services.html:22 +msgid "The date of finish service" +msgstr "Дата завершения услуги" + #: templates/clientsideapp/services.html:25 -msgid "currency" +msgid "Cost" +msgstr "Стоимость" + +#: templates/clientsideapp/services.html:35 +msgid "" +"Attantion! You have not yet a service, for use the services " +"please purchase service you want." msgstr "" +"Внимание! У вас нет услуги, для использования ресурсов " +"приобретите нужную услугу из представленных тут." + +#: templates/clientsideapp/services.html:46 +msgid "Services available for ordering" +msgstr "Доступные для заказа услуги" + +#: templates/clientsideapp/services.html:68 +msgid "No services available for ordering" +msgstr "Нет доступных для заказа услуг" #: views.py:51 #, python-format @@ -127,7 +246,7 @@ msgstr "Вы не уверены что хотите оплатить долг?" msgid "Your account have not enough money" msgstr "Недостаточно средств на счету" -#: views.py:90 +#: views.py:89 #, python-format msgid "%(username)s paid the debt %(amount).2f" msgstr "%(username)s заплатил долг в размере %(amount).2f" diff --git a/clientsideapp/templates/clientsideapp/custom_pages/footer.htm b/clientsideapp/templates/clientsideapp/custom_pages/footer.htm new file mode 100644 index 0000000..55ce634 --- /dev/null +++ b/clientsideapp/templates/clientsideapp/custom_pages/footer.htm @@ -0,0 +1 @@ +Your custom content here. \ No newline at end of file diff --git a/clientsideapp/templates/clientsideapp/custom_pages/main_page.htm b/clientsideapp/templates/clientsideapp/custom_pages/main_page.htm new file mode 100644 index 0000000..27e1fca --- /dev/null +++ b/clientsideapp/templates/clientsideapp/custom_pages/main_page.htm @@ -0,0 +1 @@ +

You can change this page

\ No newline at end of file diff --git a/clientsideapp/templates/clientsideapp/custom_pages/service.htm b/clientsideapp/templates/clientsideapp/custom_pages/service.htm new file mode 100644 index 0000000..a987da4 --- /dev/null +++ b/clientsideapp/templates/clientsideapp/custom_pages/service.htm @@ -0,0 +1,2 @@ +

Your custom content

+

You have service variable {{ active_service }}

\ No newline at end of file diff --git a/clientsideapp/templates/clientsideapp/custom_pages/service_bottom.htm b/clientsideapp/templates/clientsideapp/custom_pages/service_bottom.htm new file mode 100644 index 0000000..89cbb76 --- /dev/null +++ b/clientsideapp/templates/clientsideapp/custom_pages/service_bottom.htm @@ -0,0 +1 @@ +

Your custom content on bottom

\ No newline at end of file diff --git a/clientsideapp/templates/clientsideapp/debt_buy.html b/clientsideapp/templates/clientsideapp/debt_buy.html index 510fbe8..dc14472 100644 --- a/clientsideapp/templates/clientsideapp/debt_buy.html +++ b/clientsideapp/templates/clientsideapp/debt_buy.html @@ -18,9 +18,11 @@ {% trans 'Are you sure you want to spend a payment?' %} -

{% blocktrans %}From your account, they withdraw funds in {{ amount }} rub.
-As a result, you will remain on your account {{ ballance_after }} rubles.
-The administrator can immediately see that you shut down the debt.{% endblocktrans %}

+

{% blocktrans trimmed %} + From your account, they withdraw funds in {{ amount }} rub.
+ As a result, you will remain on your account {{ ballance_after }} rubles.
+ The administrator can immediately see that you shut down the debt. + {% endblocktrans %}

{% trans 'Description of payment' %}

@@ -38,4 +40,4 @@ The administrator can immediately see that you shut down the debt.{% endblocktra
-{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/clientsideapp/templates/clientsideapp/debts.html b/clientsideapp/templates/clientsideapp/debts.html index d7d1038..18563e3 100644 --- a/clientsideapp/templates/clientsideapp/debts.html +++ b/clientsideapp/templates/clientsideapp/debts.html @@ -1,25 +1,26 @@ {% extends 'clientsideapp/ext.html' %} +{% load i18n %} {% block client_main %} - - - - - - + + + + + + {% for debt in debts %} - + {% empty %} - + {% endfor %} diff --git a/clientsideapp/templates/clientsideapp/ext.html b/clientsideapp/templates/clientsideapp/ext.html index bdd7c4d..b9541b5 100644 --- a/clientsideapp/templates/clientsideapp/ext.html +++ b/clientsideapp/templates/clientsideapp/ext.html @@ -1,10 +1,10 @@ - +{% load globaltags %}{% load i18n %} - Личный кабинет + {% global_var 'COMPANY_NAME' %} - {% trans 'Personal account' %} @@ -19,6 +19,8 @@ + +{% url 'client_side:home' as client_side_home %} @@ -80,11 +70,12 @@
{% if request.user.is_staff %} -
- Кстати. - Вы администратор, и не должны производить действия из кабинета пользователя, производите их из админки. - Кабинет клиента для вас только для ознакомления. -
+
+ {% blocktrans trimmed %} + Attantion! + You are is admin, and do not be active here, please back to admin side. Client side to you for reference only. + {% endblocktrans %} +
{% endif %} {% include 'message_block.html' %} @@ -95,7 +86,7 @@ diff --git a/clientsideapp/templates/clientsideapp/index.html b/clientsideapp/templates/clientsideapp/index.html index a0ff0b2..a881d0e 100644 --- a/clientsideapp/templates/clientsideapp/index.html +++ b/clientsideapp/templates/clientsideapp/index.html @@ -1,73 +1,4 @@ {% extends 'clientsideapp/ext.html' %} {% block client_main %} - -
-
Наши реквизиты
-
-
    -
  • Режим работы: с 9:00 до 22:00
  • -
  • Телефоны: +79788328885, +79788318999
  • -
  • Адрес: пос. Нижнегорский, ул. Победы 38. 2й этаж старого дома быта, возле рынка
  • -
- -
-
- -
СостояниеСуммаОписаниеДата созданияДата платежаОплатить{% trans 'State' %}{% trans 'Summ' %}{% trans 'Description' %}{% trans 'Date of create' %}{% trans 'Date of pay' %}{% trans 'Pay' %}
{{ debt.amount }} руб.{{ debt.amount }} {% trans 'currency' %} {{ debt.comment }} {{ debt.date_create|date:'d b H:i' }} @@ -27,21 +28,21 @@ {{ debt.date_pay|date:'d b H:i' }} {% else %} {% if debt.status %} - Создан оплаченным + {% trans 'Created paid' %} {% else %} - Ещё не оплачен + {% trans 'Not yet paid' %} {% endif %} {% endif %} - +
У вас нет долгов {% trans 'You have no debt' %}
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
УслугаСтоимость (руб)
Выезд мастера. Цена зависит от удалённости от Нижнегорска100-200
Настройка роутера300
Установка и восстановление сетевых настроек100
Замена кабеля (витая пара)30 руб/метр
Замена конектора RJ-4550
Роутер TL-WR840N + настройка1800
Установка драйвера (с настройкой сетевых установок)100
"Что-то не работает" – по договорённости~
Замена блока питания200
Замена оптоволоконных линий связиот 500
- - + {% include 'clientsideapp/custom_pages/main_page.htm' %} {% endblock %} diff --git a/clientsideapp/templates/clientsideapp/modal_service_buy.html b/clientsideapp/templates/clientsideapp/modal_service_buy.html index 93d6d7d..5fe1d2e 100644 --- a/clientsideapp/templates/clientsideapp/modal_service_buy.html +++ b/clientsideapp/templates/clientsideapp/modal_service_buy.html @@ -6,16 +6,17 @@ + {% include 'clientsideapp/custom_pages/service_bottom.htm' %} - - -
-
-
-
- - Как работает личный кабинет, раздел услуг -
-
-

Перед вами находится 2 столбца, в левом ваша текущая подключённая услуга, а в правом доступные для заказа услуги. - Когда у вас уже есть подключённая услуга то заказать новую вы не можете пока не завершится текущая.

-

Когда у вас нет действующей подключённой услуги то кнопка заказа станет активной, и вы сможете заказать для себя услугу. - Обратите внимание что именно в этот момент с вашего счёта снимутся деньги в соответствии со стоимостью услуги. -

-
-
-
-
{% endblock %} diff --git a/clientsideapp/views.py b/clientsideapp/views.py index fe7db8b..b9ee31a 100644 --- a/clientsideapp/views.py +++ b/clientsideapp/views.py @@ -28,7 +28,7 @@ def pays(request): @login_required def services(request): try: - abon = Abon.objects.get(pk=request.user.pk) + abon = request.user all_tarifs = Tariff.objects.get_tariffs_by_group(abon.group.pk) current_service = abon.active_tariff() except Abon.DoesNotExist: @@ -43,14 +43,14 @@ def services(request): @login_required @transaction.atomic def buy_service(request, srv_id): - abon = get_object_or_404(Abon, pk=request.user.pk) + abon = request.user service = get_object_or_404(Tariff, pk=srv_id) try: current_service = abon.active_tariff() if request.method == 'POST': - abon.pick_tariff(service, request.user, _("Buy the service via user side, service '%s'") + abon.pick_tariff(service, None, _("Buy the service via user side, service '%s'") % service) - abon.abon.sync_with_nas(created=False) + abon.sync_with_nas(created=False) messages.success(request, _("The service '%s' wan successfully activated") % service.title) else: return render_to_text('clientsideapp/modal_service_buy.html', { @@ -76,7 +76,7 @@ def debts_list(request): @transaction.atomic def debt_buy(request, d_id): debt = get_object_or_404(InvoiceForPayment, id=d_id) - abon = get_object_or_404(Abon, id=request.user.id) + abon = request.user if request.method == 'POST': try: sure = request.POST.get('sure') @@ -86,7 +86,7 @@ def debt_buy(request, d_id): raise LogicError(_('Your account have not enough money')) amount = -debt.amount - abon.add_ballance(request.user, amount, comment=gettext('%(username)s paid the debt %(amount).2f') % { + abon.add_ballance(None, amount, comment=gettext('%(username)s paid the debt %(amount).2f') % { 'username': abon.get_full_name(), 'amount': amount }) diff --git a/devapp/base_intr.py b/devapp/base_intr.py index 715dcc9..db61a66 100644 --- a/devapp/base_intr.py +++ b/devapp/base_intr.py @@ -1,8 +1,11 @@ -# -*- coding: utf-8 -*- from abc import ABCMeta, abstractmethod from easysnmp import Session +class DeviceImplementationError(Exception): + pass + + class DevBase(object, metaclass=ABCMeta): def __init__(self, dev_instance=None): @@ -10,19 +13,19 @@ class DevBase(object, metaclass=ABCMeta): @staticmethod def description(): - """Возвращает текстовое описание""" + pass @abstractmethod def reboot(self): - """Перезагружает устройство""" + pass @abstractmethod def get_ports(self): - """Получаем инфу о портах""" + pass @abstractmethod def get_device_name(self): - """Получаем имя устройства по snmp""" + """Return device name by snmp""" @abstractmethod def uptime(self): @@ -30,17 +33,17 @@ class DevBase(object, metaclass=ABCMeta): @abstractmethod def get_template_name(self): - """Получаем путь к html шаблону отображения устройства""" + """Return path to html template for device""" @staticmethod @abstractmethod def has_attachable_to_subscriber(): - """Можно-ли подключать устройство к абоненту""" + """Can connect device to subscriber""" @staticmethod @abstractmethod def is_use_device_port(): - """True если при авторизации по opt82 используется порт""" + """True if used device port while opt82 authorization""" class BasePort(object, metaclass=ABCMeta): @@ -70,7 +73,7 @@ class SNMPBaseWorker(object, metaclass=ABCMeta): self.ses = Session(hostname=ip, community=community, version=ver) def set_int_value(self, oid, value): - return self.ses.set(oid, value) + return self.ses.set(oid, value, 'i') def get_list(self, oid): for v in self.ses.walk(oid): diff --git a/devapp/dev_types.py b/devapp/dev_types.py index 5db0309..4d96ee8 100644 --- a/devapp/dev_types.py +++ b/devapp/dev_types.py @@ -3,7 +3,7 @@ from django.utils.translation import gettext_lazy as _ from mydefs import RuTimedelta, safe_int from datetime import timedelta from easysnmp import EasySNMPTimeoutError -from .base_intr import DevBase, SNMPBaseWorker, BasePort +from .base_intr import DevBase, SNMPBaseWorker, BasePort, DeviceImplementationError class DLinkPort(BasePort): @@ -41,21 +41,25 @@ class DLinkDevice(DevBase, SNMPBaseWorker): return self.get_item('.1.3.6.1.4.1.2021.8.1.101.1') def get_ports(self): + interfaces_count = safe_int(self.get_item('.1.3.6.1.2.1.2.1.0')) nams = list(self.get_list('.1.3.6.1.4.1.171.10.134.2.1.1.100.2.1.3')) stats = list(self.get_list('.1.3.6.1.2.1.2.2.1.7')) macs = list(self.get_list('.1.3.6.1.2.1.2.2.1.6')) - speeds = self.get_list('.1.3.6.1.2.1.31.1.1.1.15') + speeds = list(self.get_list('.1.3.6.1.2.1.2.2.1.5')) res = [] - for n, speed in enumerate(speeds): - status = True if int(stats[n]) == 1 else False - res.append(DLinkPort( - n+1, - nams[n] if len(nams) > 0 else _('does not fetch the name'), - status, - macs[n] if len(macs) > 0 else _('does not fetch the mac'), - int(speed or 0), - self)) - return res + try: + for n in range(interfaces_count): + status = True if int(stats[n]) == 1 else False + res.append(DLinkPort( + n+1, + nams[n] if len(nams) > 0 else '', + status, + macs[n] if len(macs) > 0 else _('does not fetch the mac'), + int(speeds[n]) if len(speeds) > 0 else 0, + self)) + return res + except IndexError: + raise DeviceImplementationError('Dlink port index error') def get_device_name(self): return self.get_item('.1.3.6.1.2.1.1.1.0') diff --git a/devapp/models.py b/devapp/models.py index 4b51c06..4f0406c 100644 --- a/devapp/models.py +++ b/devapp/models.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- import requests -from django.db import models +from django.db import models, ProgrammingError from djing.fields import MACAddressField from .base_intr import DevBase from mydefs import MyGenericIPAddressField, MyChoicesAdapter, ip2int @@ -76,7 +76,7 @@ class Device(models.Model): ) verbose_name = _('Device') verbose_name_plural = _('Devices') - ordering = ['comment'] + ordering = ['id'] def get_abons(self): pass @@ -107,6 +107,7 @@ class Device(models.Model): def update_dhcp(self): if self.devtype not in ('On','Dl'): return + #raise ProgrammingError('переделать это безобразие') # FIXME: переделать это безобразие grp = self.user_group.id code = '' diff --git a/devapp/onu_register.sh b/devapp/onu_register.sh index c835550..fb64e5a 100755 --- a/devapp/onu_register.sh +++ b/devapp/onu_register.sh @@ -29,6 +29,6 @@ if grep "${MAC}" "${DHCP_PATH}/${PART_CODE}.conf" > /dev/null; then else # add new mac echo "subclass \"${PART_CODE}\" \"${MAC}\";" >> "${DHCP_PATH}/${PART_CODE}.conf" - /usr/bin/sudo /usr/bin/systemctl restart dhcpd.service + sudo systemctl restart isc-dhcp-server.service fi diff --git a/devapp/templates/devapp/custom_dev_page/ports.html b/devapp/templates/devapp/custom_dev_page/ports.html index 42f4b8b..add949a 100644 --- a/devapp/templates/devapp/custom_dev_page/ports.html +++ b/devapp/templates/devapp/custom_dev_page/ports.html @@ -18,16 +18,16 @@ {% for port in ports %} {% if port.st %} - {% if port.sp == 10 %} + {% if port.sp == 10000000 %}
10 mbps - {% elif port.sp == 100 %} + {% elif port.sp == 100000000 %}
100 mbps - {% elif port.sp == 1000 %} + {% elif port.sp == 1000000000 %}
1 gbps - {% elif port.sp == 10000 %} + {% elif port.sp == 10000000000 %}
10 gbps {% else %} diff --git a/devapp/templates/devapp/devices.html b/devapp/templates/devapp/devices.html index 8f380f2..414701a 100644 --- a/devapp/templates/devapp/devices.html +++ b/devapp/templates/devapp/devices.html @@ -41,7 +41,7 @@ - {% with can_del_dev=perms.devapp.delete_device can_ch_dev=perms.devapp.change_device grpid=dev.group.pk %} + {% with can_del_dev=perms.devapp.delete_device can_ch_dev=perms.devapp.change_device grpid=group.id %} {% for dev in devices %} diff --git a/devapp/views.py b/devapp/views.py index ee3a8cb..f4a0e12 100644 --- a/devapp/views.py +++ b/devapp/views.py @@ -13,6 +13,7 @@ from django.utils.translation import gettext_lazy as _, gettext from easysnmp import EasySNMPTimeoutError, EasySNMPError from django.views.generic import ListView, DetailView +from devapp.base_intr import DeviceImplementationError from mydefs import res_success, res_error, only_admins, ping, ip_addr_regex from abonapp.models import Abon from group_app.models import Group @@ -23,7 +24,7 @@ from guardian.shortcuts import get_objects_for_user from chatbot.telebot import send_notify from chatbot.models import ChatException from jsonview.decorators import json_view -from djing.global_base_views import HashAuthView, AllowedSubnetMixin, OrderingMixin +from djing import global_base_views from .models import Device, Port, DeviceDBException, DeviceMonitoringException from .forms import DeviceForm, PortForm from mydefs import safe_int @@ -35,7 +36,7 @@ class BaseDeviceListView(ListView): @method_decorator([login_required, only_admins], name='dispatch') -class DevicesListView(BaseDeviceListView, OrderingMixin): +class DevicesListView(BaseDeviceListView, global_base_views.OrderingMixin): context_object_name = 'devices' template_name = 'devapp/devices.html' @@ -62,7 +63,7 @@ class DevicesListView(BaseDeviceListView, OrderingMixin): @method_decorator([login_required, only_admins], name='dispatch') -class DevicesWithoutGroupsListView(BaseDeviceListView, OrderingMixin): +class DevicesWithoutGroupsListView(BaseDeviceListView, global_base_views.OrderingMixin): context_object_name = 'devices' template_name = 'devapp/devices_null_group.html' queryset = Device.objects.filter(group=None).only('comment', 'devtype', 'pk', 'ip_address') @@ -179,7 +180,7 @@ def manage_ports(request, device_id): @method_decorator([login_required, only_admins], name='dispatch') -class ShowSubscriberOnPort(DetailView): +class ShowSubscriberOnPort(global_base_views.RedirectWhenErrorMixin, DetailView): template_name = 'devapp/manage_ports/modal_show_subscriber_on_port.html' http_method_names = ['get'] @@ -190,6 +191,14 @@ class ShowSubscriberOnPort(DetailView): obj = Abon.objects.get(device_id=dev_id, dev_port_id=port_id) except Abon.DoesNotExist: raise Http404(gettext('Subscribers on port does not exist')) + except Abon.MultipleObjectsReturned: + errmsg = gettext('More than one subscriber on device port') + # messages.error(self.request, errmsg) + raise global_base_views.RedirectWhenError( + resolve_url('devapp:fix_port_conflict', group_id=self.kwargs.get('group_id'), device_id=dev_id, + port_id=port_id), + errmsg + ) return obj @@ -370,7 +379,7 @@ def devview(request, device_id): }) except EasySNMPError: messages.error(request, _('SNMP error on device')) - except DeviceDBException as e: + except (DeviceDBException, DeviceImplementationError) as e: messages.error(request, e) return render(request, 'devapp/custom_dev_page/' + template_name, { 'dev': dev @@ -399,7 +408,7 @@ def toggle_port(request, device_id, portid, status=0): except EasySNMPTimeoutError: messages.error(request, _('wait for a reply from the SNMP Timeout')) except EasySNMPError as e: - messages.error(request, e) + messages.error(request, 'EasySNMPError: %s' % e) return redirect('devapp:view', dev.group.pk if dev.group is not None else 0, device_id) @@ -466,8 +475,8 @@ def fix_onu(request): if parent is not None: manobj = parent.get_manager_object() ports = manobj.get_list_keyval('.1.3.6.1.4.1.3320.101.10.1.1.3') - text = ' ' %\ - (_('Device with mac address %(mac)s does not exist') % {'mac': mac}) + text = ' ' % \ + (_('Device with mac address %(mac)s does not exist') % {'mac': mac}) for srcmac, snmpnum in ports: # convert bytes mac address to str presentation mac address real_mac = ':'.join(['%x' % ord(i) for i in srcmac]) @@ -501,7 +510,7 @@ def fix_port_conflict(request, group_id, device_id, port_id): }) -class OnDevDown(AllowedSubnetMixin, HashAuthView): +class OnDevDown(global_base_views.AllowedSubnetMixin, global_base_views.HashAuthView): # # Api view for monitoring devices # diff --git a/djing/auth_backends.py b/djing/auth_backends.py new file mode 100644 index 0000000..4517b04 --- /dev/null +++ b/djing/auth_backends.py @@ -0,0 +1,33 @@ +from django.contrib.auth.backends import ModelBackend +from accounts_app.models import BaseAccount, UserProfile +from abonapp.models import Abon + + +class CustomAuthBackend(ModelBackend): + def authenticate(self, request, username=None, password=None, **kwargs): + if username is None: + username = kwargs.get(BaseAccount.USERNAME_FIELD) + try: + user = BaseAccount._default_manager.get_by_natural_key(username) + if user.check_password(password): + if user.is_staff: + auser = UserProfile.objects.get_by_natural_key(username) + else: + auser = Abon.objects.get_by_natural_key(username) + if self.user_can_authenticate(auser): + return auser + except BaseAccount.DoesNotExist: + # Run the default password hasher once to reduce the timing + # difference between an existing and a non-existing user (#20760). + BaseAccount().set_password(password) + + def get_user(self, user_id): + try: + user = BaseAccount._default_manager.get(pk=user_id) + if user.is_staff: + user = UserProfile._default_manager.get(pk=user_id) + else: + user = Abon._default_manager.get(pk=user_id) + except BaseAccount.DoesNotExist: + return None + return user if self.user_can_authenticate(user) else None diff --git a/djing/global_base_views.py b/djing/global_base_views.py index 5eeb350..51040f2 100644 --- a/djing/global_base_views.py +++ b/djing/global_base_views.py @@ -1,14 +1,27 @@ from hashlib import sha256 +from json import dumps from django.views.generic.base import View -from django.http.response import HttpResponseForbidden +from django.http.response import HttpResponseForbidden, Http404, HttpResponseRedirect, HttpResponse +from django.utils.translation import gettext_lazy as _ from django.conf import settings +from django.views.generic import ListView from netaddr import IPNetwork, IPAddress - +from django.core.paginator import InvalidPage, EmptyPage API_AUTH_SECRET = getattr(settings, 'API_AUTH_SECRET') API_AUTH_SUBNET = getattr(settings, 'API_AUTH_SUBNET') +class RedirectWhenError(Exception): + def __init__(self, url, failed_message=None): + self.url = url + if failed_message is not None: + self.message = failed_message + + def __str__(self): + return self.message or '' + + class HashAuthView(View): @staticmethod @@ -87,4 +100,54 @@ class OrderingMixin(object): if direction == 'down': dfx = '-' if order_by: - return ["%s%s" % (dfx, order_by)] + return "%s%s" % (dfx, order_by) + + +class RedirectWhenErrorMixin(object): + + def get(self, request, *args, **kwargs): + try: + return super(RedirectWhenErrorMixin, self).get(request, *args, **kwargs) + except RedirectWhenError as e: + if request.is_ajax(): + return HttpResponse(dumps({ + 'url': e.url, + 'text': e.message or '' + })) + else: + return HttpResponseRedirect(e.url) + + +class BaseListWithFiltering(RedirectWhenErrorMixin, ListView): + """ + When queryset contains filter and pagination than data may be missing, + and original code is raising 404 error. We want to redirect without pagination. + """ + + def paginate_queryset(self, queryset, page_size): + paginator = self.get_paginator( + queryset, page_size, orphans=self.get_paginate_orphans(), + allow_empty_first_page=self.get_allow_empty()) + page_kwarg = self.page_kwarg + page = self.kwargs.get(page_kwarg) or self.request.GET.get(page_kwarg) or 1 + try: + page_number = int(page) + except ValueError: + if page == 'last': + page_number = paginator.num_pages + else: + raise Http404(_("Page is not 'last', nor can it be converted to an int.")) + try: + page = paginator.page(page_number) + return paginator, page, page.object_list, page.has_other_pages() + except EmptyPage: + # remove pagination from url + url = self.request.GET.copy() + del url[self.page_kwarg] + raise RedirectWhenError("%s?%s" % (self.request.path, url.urlencode()), + _('Filter does not contains data, filter without pagination')) + except InvalidPage as e: + raise Http404(_('Invalid page (%(page_number)s): %(message)s') % { + 'page_number': page_number, + 'message': str(e) + }) diff --git a/djing/local_settings.py.template b/djing/local_settings.py.template index 8f251a4..f3ac692 100644 --- a/djing/local_settings.py.template +++ b/djing/local_settings.py.template @@ -57,3 +57,5 @@ API_AUTH_SECRET = 'your api secret' # Allowed subnet for api API_AUTH_SUBNET = '127.0.0.0/8' +# Company name +COMPANY_NAME = 'Your company name' diff --git a/djing/settings.py b/djing/settings.py index 293e8fc..8e31870 100644 --- a/djing/settings.py +++ b/djing/settings.py @@ -19,13 +19,14 @@ from django.urls import reverse_lazy SECRET_KEY = local_settings.SECRET_KEY # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = local_settings.DEBUG or False +DEBUG = local_settings.DEBUG ALLOWED_HOSTS = local_settings.ALLOWED_HOSTS # required for django-guardian AUTHENTICATION_BACKENDS = ( - 'django.contrib.auth.backends.ModelBackend', # default + 'djing.auth_backends.CustomAuthBackend', + #'django.contrib.auth.backends.ModelBackend', # default 'guardian.backends.ObjectPermissionBackend' ) @@ -78,14 +79,16 @@ TEMPLATES = [ 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ - 'django.template.context_processors.debug', + #'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', 'taskapp.context_proc.get_active_tasks_count', - 'global_context_processors.context_processor_additional_profile', 'msg_app.context_processors.get_new_messages_count' ], + 'libraries': { + 'globaltags': 'djing.templatetags.globaltags', + } }, }, ] @@ -117,7 +120,7 @@ AUTH_PASSWORD_VALIDATORS = [ }, ] -SESSION_ENGINE = 'django.contrib.sessions.backends.file' +SESSION_ENGINE = 'django.contrib.sessions.backends.db' SESSION_COOKIE_HTTPONLY = True @@ -187,3 +190,6 @@ API_AUTH_SECRET = local_settings.API_AUTH_SECRET # Allowed subnet for api API_AUTH_SUBNET = local_settings.API_AUTH_SUBNET + +# Company name +COMPANY_NAME = local_settings.COMPANY_NAME diff --git a/djing/templatetags/__init__.py b/djing/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/djing/templatetags/globaltags.py b/djing/templatetags/globaltags.py new file mode 100644 index 0000000..9b36fd9 --- /dev/null +++ b/djing/templatetags/globaltags.py @@ -0,0 +1,9 @@ +from django import template +from django.conf import settings + +register = template.Library() + + +@register.simple_tag +def global_var(var_name): + return getattr(settings, var_name, '') diff --git a/docs/views.md b/docs/views.md index cdf902e..2c3c13e 100644 --- a/docs/views.md +++ b/docs/views.md @@ -33,7 +33,7 @@ class PaysListView(ListView): На примере *devapp/devices.html* можно рассмотреть такую сортировку. Там указан url с параметрами -> {% url 'devapp:devs' group.pk %}?order_by=ip_address&dir={{ dir|default:'down' }} +> \{\% url 'devapp:devs' group.pk \%\}?order_by=ip_address&dir=\{\{ dir|default:'down' \}\} Тут обратная сортировка по полю *ip_address*, со знаком -. То есть эквивалент будет выглядеть примерно так: > Device.objects.all().order_by('-ip_address') diff --git a/global_context_processors.py b/global_context_processors.py deleted file mode 100644 index 995a717..0000000 --- a/global_context_processors.py +++ /dev/null @@ -1,12 +0,0 @@ -# -*- coding: utf-8 -*- -from django.shortcuts import get_object_or_404 -from abonapp.models import Abon -from django.conf import settings - - -# От сюда можно получать на клиентской стороне профиль абонента -def context_processor_additional_profile(request): - if request.user.is_staff or request.user.is_anonymous(): - return {'subscriber': request.user, 'FILE_UPLOAD_MAX_MEMORY_SIZE': settings.FILE_UPLOAD_MAX_MEMORY_SIZE} - else: - return {'subscriber': get_object_or_404(Abon, id=request.user.pk), 'FILE_UPLOAD_MAX_MEMORY_SIZE': settings.FILE_UPLOAD_MAX_MEMORY_SIZE} diff --git a/install_debian.sh b/install_debian.sh index 2cada99..397248e 100644 --- a/install_debian.sh +++ b/install_debian.sh @@ -1,3 +1,3 @@ /usr/bin/bash - -apt-get install mariadb-server mariadb-client libmysqlclient-dev python3-dev python3-pip python3-pillow uwsgi nginx uwsgi-plugin-python3 libsnmp-dev git +apt-get install mariadb-server mariadb-client default-libmysqlclient-dev python3-dev python3-pip python3-pil uwsgi nginx uwsgi-plugin-python3 libsnmp-dev git gettext diff --git a/mapapp/templates/maps/dot.html b/mapapp/templates/maps/dot.html index a1396b7..a063840 100644 --- a/mapapp/templates/maps/dot.html +++ b/mapapp/templates/maps/dot.html @@ -1,6 +1,7 @@ {% extends 'base.html' %} {% load i18n %} {% load bootstrap3 %} +{% load globaltags %} {% block main %}