Browse Source

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

devel
www-data 8 years ago
parent
commit
8694b6d237
  1. 3
      abonapp/locale/ru/LC_MESSAGES/django.po
  2. 8
      abonapp/models.py
  3. 3
      abonapp/pay_systems.py
  4. 7
      abonapp/templates/abonapp/log.html
  5. 12
      abonapp/templates/abonapp/peoples.html
  6. 27
      abonapp/views.py
  7. 2
      accounts_app/views.py
  8. 7
      agent/netflow/netflow_handler.py
  9. 2
      chatbot/models.py
  10. 169
      clientsideapp/locale/ru/LC_MESSAGES/django.po
  11. 1
      clientsideapp/templates/clientsideapp/custom_pages/footer.htm
  12. 1
      clientsideapp/templates/clientsideapp/custom_pages/main_page.htm
  13. 2
      clientsideapp/templates/clientsideapp/custom_pages/service.htm
  14. 1
      clientsideapp/templates/clientsideapp/custom_pages/service_bottom.htm
  15. 6
      clientsideapp/templates/clientsideapp/debt_buy.html
  16. 25
      clientsideapp/templates/clientsideapp/debts.html
  17. 47
      clientsideapp/templates/clientsideapp/ext.html
  18. 71
      clientsideapp/templates/clientsideapp/index.html
  19. 7
      clientsideapp/templates/clientsideapp/modal_service_buy.html
  20. 44
      clientsideapp/templates/clientsideapp/services.html
  21. 12
      clientsideapp/views.py
  22. 21
      devapp/base_intr.py
  23. 14
      devapp/dev_types.py
  24. 5
      devapp/models.py
  25. 2
      devapp/onu_register.sh
  26. 8
      devapp/templates/devapp/custom_dev_page/ports.html
  27. 2
      devapp/templates/devapp/devices.html
  28. 23
      devapp/views.py
  29. 33
      djing/auth_backends.py
  30. 69
      djing/global_base_views.py
  31. 2
      djing/local_settings.py.template
  32. 16
      djing/settings.py
  33. 0
      djing/templatetags/__init__.py
  34. 9
      djing/templatetags/globaltags.py
  35. 2
      docs/views.md
  36. 12
      global_context_processors.py
  37. 2
      install_debian.sh
  38. 3
      mapapp/templates/maps/dot.html
  39. 4
      mapapp/templates/maps/modal_add_dot.html
  40. 359
      migrate_to_0.2.py
  41. 6
      msg_app/models.py
  42. 6
      queue_mngr.py
  43. BIN
      static/clientside/bc.png
  44. 16
      static/clientside/custom.css
  45. BIN
      static/img/bcgr.png
  46. 15
      static/js/my.js
  47. 2
      systemd_units/djing.service
  48. 2
      systemd_units/djing_queue.service
  49. 2
      systemd_units/djing_rotate.service
  50. 2
      systemd_units/djing_telebot.service
  51. 21
      systemd_units/webdav_backup.py
  52. 5
      taskapp/context_proc.py
  53. 2
      taskapp/handle.py
  54. 6
      taskapp/locale/ru/LC_MESSAGES/django.po
  55. 3
      taskapp/templates/taskapp/add_edit_task.html
  56. 15
      taskapp/templates/taskapp/tasklist.html
  57. 3
      taskapp/templates/taskapp/tasklist_all.html
  58. 10
      taskapp/templates/taskapp/tasklist_failed.html
  59. 2
      taskapp/templates/taskapp/tasklist_own.html
  60. 5
      taskapp/views.py
  61. 6
      templates/all_base.html

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

@ -1138,3 +1138,6 @@ msgstr "Абоненты"
msgid "Successfully saved" msgid "Successfully saved"
msgstr "Успешно сохранено" msgstr "Успешно сохранено"
msgid "This user can not buy admin services"
msgstr "Этот пользователь не может назначать административные услуги"

8
abonapp/models.py

@ -191,7 +191,7 @@ class Abon(BaseAccount):
AbonLog.objects.create( AbonLog.objects.create(
abon=self, abon=self,
amount=amount, amount=amount,
author=current_user,
author=current_user if isinstance(current_user, UserProfile) else None,
comment=comment comment=comment
) )
self.ballance += amount self.ballance += amount
@ -202,8 +202,11 @@ class Abon(BaseAccount):
amount = round(tariff.amount, 2) amount = round(tariff.amount, 2)
if not author.is_staff and tariff.is_admin:
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')) 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 is not None:
if self.current_tariff.tariff == tariff: if self.current_tariff.tariff == tariff:
@ -347,7 +350,6 @@ class AllTimePayLogManager(models.Manager):
r = cur.fetchone() r = cur.fetchone()
if r is None: break if r is None: break
summ, dat = r summ, dat = r
print(summ, dat)
yield {'summ': summ, 'pay_date': datetime.strptime(dat, '%Y-%m-%d')} yield {'summ': summ, 'pay_date': datetime.strptime(dat, '%Y-%m-%d')}

3
abonapp/pay_systems.py

@ -67,8 +67,7 @@ def allpay(request):
if pays.count() > 0: if pays.count() > 0:
return bad_ret(-100) 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']) abon.save(update_fields=['ballance'])
AllTimePayLog.objects.create( AllTimePayLog.objects.create(

7
abonapp/templates/abonapp/log.html

@ -39,7 +39,12 @@
</td> </td>
<td>{{ l.comment }}</td> <td>{{ l.comment }}</td>
<td>{{ l.date|date:"D d E Y H:i:s" }}</td> <td>{{ l.date|date:"D d E Y H:i:s" }}</td>
<td><a href="{% url 'acc_app:other_profile' l.author.id %}">{{ l.author.username }}</a></td>
<td>
{% if l.author %}
<a href="{% url 'acc_app:other_profile' l.author.id %}">{{ l.author.username }}</a></td>
{% else %}
---
{% endif %}
</tr> </tr>
{% empty %} {% empty %}
<tr> <tr>

12
abonapp/templates/abonapp/peoples.html

@ -63,7 +63,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% 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 %} {% for human in peoples %}
{% if human.is_active %} {% if human.is_active %}
<tr> <tr>
@ -94,11 +94,11 @@
<td>{{ human.house|default:'-' }}</td> <td>{{ human.house|default:'-' }}</td>
<td><a href="tel:{{ human.telephone }}">{{ human.telephone }}</a></td> <td><a href="tel:{{ human.telephone }}">{{ human.telephone }}</a></td>
<td> <td>
{% if human.active_tariff %}
{% if perms.tariff_app.change_tariff %}
<a href="{% url 'tarifs:edit' human.active_tariff.tariff.pk %}">{{ human.active_tariff.tariff.title }}</a>
{% if human.current_tariff %}
{% if can_ch_trf %}
<a href="{% url 'tarifs:edit' human.current_tariff.tariff.pk %}">{{ human.current_tariff.tariff.title }}</a>
{% else %} {% else %}
{{ human.active_tariff.tariff.title }}
{{ human.current_tariff.tariff.title }}
{% endif %} {% endif %}
{% else %}&mdash;&mdash;&mdash; {% else %}&mdash;&mdash;&mdash;
{% endif %} {% endif %}
@ -109,7 +109,7 @@
{% endfor %} {% endfor %}
</td> </td>
<td class="hidden-xs"> <td class="hidden-xs">
{% if can_del_trf %}
{% if can_del_abon %}
<a href="{% url 'abonapp:del_abon' %}?id={{ human.pk }}" class="btn btn-danger btn-sm"> <a href="{% url 'abonapp:del_abon' %}?id={{ human.pk }}" class="btn btn-danger btn-sm">
<span class="glyphicon glyphicon-remove"></span> <span class="glyphicon glyphicon-remove"></span>
</a> </a>

27
abonapp/views.py

@ -6,7 +6,7 @@ from django.db import IntegrityError, ProgrammingError, transaction
from django.db.models import Count, Q from django.db.models import Count, Q
from django.shortcuts import render, redirect, get_object_or_404, resolve_url from django.shortcuts import render, redirect, get_object_or_404, resolve_url
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.http import HttpResponse, HttpResponseBadRequest
from django.http import HttpResponse, Http404
from django.contrib import messages from django.contrib import messages
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
@ -27,17 +27,17 @@ from statistics.models import getModel
from group_app.models import Group from group_app.models import Group
from guardian.shortcuts import get_objects_for_user, assign_perm from guardian.shortcuts import get_objects_for_user, assign_perm
from guardian.decorators import permission_required_or_403 as permission_required 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) 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 paginate_by = PAGINATION_ITEMS_PER_PAGE
http_method_names = ['get'] http_method_names = ['get']
@method_decorator([login_required, mydefs.only_admins], name='dispatch')
class PeoplesListView(BaseAbonListView): class PeoplesListView(BaseAbonListView):
context_object_name = 'peoples' context_object_name = 'peoples'
template_name = 'abonapp/peoples.html' template_name = 'abonapp/peoples.html'
@ -45,7 +45,7 @@ class PeoplesListView(BaseAbonListView):
def get_queryset(self): def get_queryset(self):
street_id = mydefs.safe_int(self.request.GET.get('street')) street_id = mydefs.safe_int(self.request.GET.get('street'))
gid = mydefs.safe_int(self.kwargs.get('gid')) 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: if street_id > 0:
peoples_list = peoples_list.filter(group__pk=gid, street=street_id) peoples_list = peoples_list.filter(group__pk=gid, street=street_id)
else: else:
@ -60,7 +60,11 @@ class PeoplesListView(BaseAbonListView):
pass pass
except mydefs.LogicError as e: except mydefs.LogicError as e:
messages.warning(self.request, 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 return peoples_list
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
@ -79,6 +83,7 @@ class PeoplesListView(BaseAbonListView):
return context return context
@method_decorator([login_required, mydefs.only_admins], name='dispatch')
class GroupListView(BaseAbonListView): class GroupListView(BaseAbonListView):
context_object_name = 'groups' context_object_name = 'groups'
template_name = 'abonapp/group_list.html' template_name = 'abonapp/group_list.html'
@ -186,6 +191,7 @@ def abonamount(request, gid, uid):
}, request=request) }, 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') @method_decorator(permission_required('group_app.can_view_group', (Group, 'pk', 'gid')), name='dispatch')
class DebtsListView(BaseAbonListView): class DebtsListView(BaseAbonListView):
context_object_name = 'invoices' context_object_name = 'invoices'
@ -203,6 +209,7 @@ class DebtsListView(BaseAbonListView):
return context 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') @method_decorator(permission_required('group_app.can_view_group', (Group, 'pk', 'gid')), name='dispatch')
class PayHistoryListView(BaseAbonListView): class PayHistoryListView(BaseAbonListView):
context_object_name = 'pay_history' context_object_name = 'pay_history'
@ -211,7 +218,7 @@ class PayHistoryListView(BaseAbonListView):
def get_queryset(self): def get_queryset(self):
abon = get_object_or_404(models.Abon, pk=self.kwargs.get('uid')) abon = get_object_or_404(models.Abon, pk=self.kwargs.get('uid'))
self.abon = abon 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 return pay_history
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
@ -402,8 +409,8 @@ def unsubscribe_service(request, gid, uid, abon_tariff_id):
try: try:
abon = get_object_or_404(models.Abon, pk=uid) abon = get_object_or_404(models.Abon, pk=uid)
abon_tariff = get_object_or_404(models.AbonTariff, pk=int(abon_tariff_id)) abon_tariff = get_object_or_404(models.AbonTariff, pk=int(abon_tariff_id))
abon_tariff.delete()
abon.sync_with_nas(created=False) abon.sync_with_nas(created=False)
abon_tariff.delete()
messages.success(request, _('User has been detached from service')) messages.success(request, _('User has been detached from service'))
except NasFailedResult as e: except NasFailedResult as e:
messages.error(request, e) messages.error(request, e)
@ -545,7 +552,8 @@ def clear_dev(request, gid, uid):
try: try:
abon = models.Abon.objects.get(pk=uid) abon = models.Abon.objects.get(pk=uid)
abon.device = None 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')) messages.success(request, _('Device has successfully unattached'))
except models.Abon.DoesNotExist: except models.Abon.DoesNotExist:
messages.error(request, _('Abon does not exist')) 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): class DialsListView(BaseAbonListView):
context_object_name = 'logs' context_object_name = 'logs'
template_name = 'abonapp/dial_log.html' template_name = 'abonapp/dial_log.html'

2
accounts_app/views.py

@ -213,7 +213,7 @@ def perms(request, uid):
klasses = ( klasses = (
'abonapp.Abon', 'accounts_app.UserProfile', 'abonapp.Abon', 'accounts_app.UserProfile',
'abonapp.AbonTariff', 'abonapp.AbonStreet', 'devapp.Device', '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', { return render(request, 'accounts/perms/objects_types.html', {
'userprofile': userprofile, 'userprofile': userprofile,

7
agent/netflow/netflow_handler.py

@ -4,6 +4,7 @@ import sys
import os import os
from importlib import import_module from importlib import import_module
if __name__ == '__main__': if __name__ == '__main__':
if len(sys.argv) < 2: if len(sys.argv) < 2:
print("File name of netflow required") print("File name of netflow required")
@ -35,7 +36,7 @@ if __name__ == '__main__':
sql = r'SELECT abonent.ip_address, acc.username ' \ sql = r'SELECT abonent.ip_address, acc.username ' \
r'FROM abonent ' \ 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' r'WHERE abonent.ip_address != 0'
ln = cursor.execute(sql) ln = cursor.execute(sql)
with open(tmp_ipuser_file, 'w') as f: with open(tmp_ipuser_file, 'w') as f:
@ -48,8 +49,8 @@ if __name__ == '__main__':
db.close() db.close()
os.system( 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"' % { '/usr/bin/mysql -u%(DB_USER)s -h %(HOST)s -p %(DB_NAME)s --password=%(DB_PASSW)s"' % {
'CUR_DIR': cur_dir, 'CUR_DIR': cur_dir,
'TMP_IPUSER_FILE': tmp_ipuser_file, 'TMP_IPUSER_FILE': tmp_ipuser_file,

2
chatbot/models.py

@ -57,7 +57,7 @@ class MessageQueue(models.Model):
('r', 'Read') ('r', 'Read')
) )
status = models.CharField(_('Status of message'), max_length=1, choices=STATUSES, default='n') 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') tag = models.CharField(_('App tag'), max_length=6, default='none')
objects = MessageQueueManager() objects = MessageQueueManager()

169
clientsideapp/locale/ru/LC_MESSAGES/django.po

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Dmitry Novikov nerosketch@gmail.com\n" "Last-Translator: Dmitry Novikov nerosketch@gmail.com\n"
"Language: ru\n" "Language: ru\n"
@ -32,34 +32,113 @@ msgid "Are you sure you want to spend a payment?"
msgstr "Вы уверены что хотите провести платёж?" msgstr "Вы уверены что хотите провести платёж?"
#: templates/clientsideapp/debt_buy.html:21 #: templates/clientsideapp/debt_buy.html:21
#, fuzzy, python-format
#| msgid ""
#| "From your account, they withdraw funds in <b>%(amount)s</b> rub. <br/>\n"
#| "As a result, you will remain on your account %(ballance_after)s rubles. "
#| "<br/>\n"
#| "The administrator can immediately see that you shut down the debt."
#, python-format
msgid "" msgid ""
"From your account, they withdraw funds in <b>%(amount)s</b> rub.<br/>\n"
"As a result, you will remain on your account %(ballance_after)s rubles.<br/"
">\n"
"The administrator can immediately see that you shut down the debt."
"From your account, they withdraw funds in <b>%(amount)s</b> rub.<br/> As a "
"result, you will remain on your account %(ballance_after)s rubles.<br/> The "
"administrator can immediately see that you shut down the debt."
msgstr "" msgstr ""
"С вашего счёта снимутся средства в размере <b>%(amount)s</b> руб.<br/>\n"
"В результате у вас на счету останется %(ballance_after)s руб.<br/>\n"
"Администратор сразу сможет видеть что у вас закрыта задолженность."
"С вашего счёта снимутся средства в размере <b>%(amount)s</b> руб.<br/>В "
"результате у вас на счету останется %(ballance_after)s руб.<br/"
">Администратор сразу сможет видеть что у вас закрыта задолженность."
#: templates/clientsideapp/debt_buy.html:24
#: templates/clientsideapp/debt_buy.html:26
msgid "Description of payment" msgid "Description of payment"
msgstr "Описание платежа" msgstr "Описание платежа"
#: templates/clientsideapp/debt_buy.html:32
#: templates/clientsideapp/debt_buy.html:34
msgid "Confirm" msgid "Confirm"
msgstr "Подтвердить" msgstr "Подтвердить"
#: templates/clientsideapp/debt_buy.html:35
#: templates/clientsideapp/debt_buy.html:37
msgid "Cancel" msgid "Cancel"
msgstr "Отменить" 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 <b>%(ballance)s</b>"
msgstr "Ваш балланс <b>%(ballance)s</b>"
#: templates/clientsideapp/ext.html:74
msgid ""
"<strong>Attantion!</strong> You are is admin, and do not be active here, "
"please back to admin side. Client side to you for reference only."
msgstr ""
"<strong>Кстати.</strong> Вы администратор, и не должны производить действия "
"из кабинета пользователя, производите их из админки. Кабинет клиента для вас "
"только для ознакомления."
#: templates/clientsideapp/modal_service_buy.html:5 #: templates/clientsideapp/modal_service_buy.html:5
msgid "Pick service" msgid "Pick service"
msgstr "Заказать услугу" msgstr "Заказать услугу"
@ -68,20 +147,32 @@ msgstr "Заказать услугу"
msgid "Are you sure you want to order the service?" msgid "Are you sure you want to order the service?"
msgstr "Вы уверены что хотите заказать услугу?" msgstr "Вы уверены что хотите заказать услугу?"
#: templates/clientsideapp/modal_service_buy.html:9
msgid ""
"Be careful, after purchasing the service you will <b>withdraw money</b>, and "
"you will be able to use the purchased service."
msgstr ""
"Будте внимательны, после заказа услуги с вашего счёта <b>снимутся средства</"
"b>, и вы сможете пользоваться купленной услугой."
#: templates/clientsideapp/modal_service_buy.html:15 #: templates/clientsideapp/modal_service_buy.html:15
#, python-format #, python-format
msgid "" msgid ""
"Inbound speed: %(speedIn)s MBit/s<br>\n"
"Outgoing speed: %(speedOut)s MBit/s<br>\n"
"Cost: %(amount)s rubles."
"Inbound speed: %(speedIn)s MBit/s<br> Outgoing speed: %(speedOut)s MBit/"
"s<br> Cost: %(amount)s rubles."
msgstr "" 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" msgid "Pick"
msgstr "Заказать" msgstr "Заказать"
#: templates/clientsideapp/modal_service_buy.html:24
#: templates/clientsideapp/modal_service_buy.html:25
msgid "Close" msgid "Close"
msgstr "Закрыть" msgstr "Закрыть"
@ -105,9 +196,37 @@ msgstr "Комментарий"
msgid "You have not spent payments" msgid "You have not spent payments"
msgstr "У вас нет проведённых платежей" 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 #: templates/clientsideapp/services.html:25
msgid "currency"
msgid "Cost"
msgstr "Стоимость"
#: templates/clientsideapp/services.html:35
msgid ""
"<strong>Attantion!</strong> You have not yet a service, for use the services "
"please purchase service you want."
msgstr "" msgstr ""
"<strong>Внимание!</strong> У вас нет услуги, для использования ресурсов "
"приобретите нужную услугу из представленных тут."
#: 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 #: views.py:51
#, python-format #, python-format
@ -127,7 +246,7 @@ msgstr "Вы не уверены что хотите оплатить долг?"
msgid "Your account have not enough money" msgid "Your account have not enough money"
msgstr "Недостаточно средств на счету" msgstr "Недостаточно средств на счету"
#: views.py:90
#: views.py:89
#, python-format #, python-format
msgid "%(username)s paid the debt %(amount).2f" msgid "%(username)s paid the debt %(amount).2f"
msgstr "%(username)s заплатил долг в размере %(amount).2f" msgstr "%(username)s заплатил долг в размере %(amount).2f"

1
clientsideapp/templates/clientsideapp/custom_pages/footer.htm

@ -0,0 +1 @@
Your custom content <i>here</i>.

1
clientsideapp/templates/clientsideapp/custom_pages/main_page.htm

@ -0,0 +1 @@
<h1>You can change this page</h1>

2
clientsideapp/templates/clientsideapp/custom_pages/service.htm

@ -0,0 +1,2 @@
<h1>Your custom content</h1>
<p>You have service variable {{ active_service }}</p>

1
clientsideapp/templates/clientsideapp/custom_pages/service_bottom.htm

@ -0,0 +1 @@
<h1>Your custom content on bottom</h1>

6
clientsideapp/templates/clientsideapp/debt_buy.html

@ -18,9 +18,11 @@
{% trans 'Are you sure you want to spend a payment?' %} {% trans 'Are you sure you want to spend a payment?' %}
</label> </label>
</div> </div>
<p>{% blocktrans %}From your account, they withdraw funds in <b>{{ amount }}</b> rub.<br/>
<p>{% blocktrans trimmed %}
From your account, they withdraw funds in <b>{{ amount }}</b> rub.<br/>
As a result, you will remain on your account {{ ballance_after }} rubles.<br/> As a result, you will remain on your account {{ ballance_after }} rubles.<br/>
The administrator can immediately see that you shut down the debt.{% endblocktrans %}</p>
The administrator can immediately see that you shut down the debt.
{% endblocktrans %}</p>
<h4>{% trans 'Description of payment' %}</h4> <h4>{% trans 'Description of payment' %}</h4>
<div class="alert alert-info"> <div class="alert alert-info">

25
clientsideapp/templates/clientsideapp/debts.html

@ -1,25 +1,26 @@
{% extends 'clientsideapp/ext.html' %} {% extends 'clientsideapp/ext.html' %}
{% load i18n %}
{% block client_main %} {% block client_main %}
<div class="page-header"> <div class="page-header">
<h3>Ваши долги</h3>
<h3>{% trans 'Your debt' %}</h3>
</div> </div>
<table class="table table-striped table-bordered"> <table class="table table-striped table-bordered">
<thead> <thead>
<tr> <tr>
<th class="col-xs-1">Состояние</th>
<th>Сумма</th>
<th>Описание</th>
<th>Дата создания</th>
<th>Дата платежа</th>
<th class="col-xs-1">Оплатить</th>
<th class="col-xs-1">{% trans 'State' %}</th>
<th>{% trans 'Summ' %}</th>
<th>{% trans 'Description' %}</th>
<th>{% trans 'Date of create' %}</th>
<th>{% trans 'Date of pay' %}</th>
<th class="col-xs-1">{% trans 'Pay' %}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for debt in debts %} {% for debt in debts %}
<tr> <tr>
<td class="text-center"><span class="glyphicon glyphicon-{{ debt.status|yesno:'ok,time' }}"></span></td> <td class="text-center"><span class="glyphicon glyphicon-{{ debt.status|yesno:'ok,time' }}"></span></td>
<td>{{ debt.amount }} руб.</td>
<td>{{ debt.amount }} {% trans 'currency' %}</td>
<td>{{ debt.comment }}</td> <td>{{ debt.comment }}</td>
<td>{{ debt.date_create|date:'d b H:i' }}</td> <td>{{ debt.date_create|date:'d b H:i' }}</td>
<td> <td>
@ -27,21 +28,21 @@
{{ debt.date_pay|date:'d b H:i' }} {{ debt.date_pay|date:'d b H:i' }}
{% else %} {% else %}
{% if debt.status %} {% if debt.status %}
Создан оплаченным
{% trans 'Created paid' %}
{% else %} {% else %}
Ещё не оплачен
{% trans 'Not yet paid' %}
{% endif %} {% endif %}
{% endif %} {% endif %}
</td> </td>
<td class="text-center"> <td class="text-center">
<a href="{% url 'client_side:debt_buy' debt.id %}" class="btn btn-sm btn-success" title="Оплатить">
<a href="{% url 'client_side:debt_buy' debt.id %}" class="btn btn-sm btn-success" title="{% trans 'Pay' %}">
<span class="glyphicon glyphicon-usd"></span> <span class="glyphicon glyphicon-usd"></span>
</a> </a>
</td> </td>
</tr> </tr>
{% empty %} {% empty %}
<tr> <tr>
<td colspan="6">У вас нет долгов <span class="glyphicon glyphicon-ok"></span></td>
<td colspan="6">{% trans 'You have no debt' %} <span class="glyphicon glyphicon-ok"></span></td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>

47
clientsideapp/templates/clientsideapp/ext.html

@ -1,10 +1,10 @@
<!DOCTYPE HTML>
<!DOCTYPE HTML>{% load globaltags %}{% load i18n %}
<html lang="ru"> <html lang="ru">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>Личный кабинет</title>
<title>{% global_var 'COMPANY_NAME' %} - {% trans 'Personal account' %}</title>
<link href="/static/css/bootstrap.min.css" rel="stylesheet"> <link href="/static/css/bootstrap.min.css" rel="stylesheet">
<link href="/static/clientside/custom.css" rel="stylesheet"> <link href="/static/clientside/custom.css" rel="stylesheet">
<script src="/static/js/jquery-2.2.4.min.js"></script> <script src="/static/js/jquery-2.2.4.min.js"></script>
@ -19,6 +19,8 @@
<div class="modal-content" id="modContent"></div> <div class="modal-content" id="modContent"></div>
</div> </div>
</div> </div>
{% url 'client_side:home' as client_side_home %}
<!-- Fixed navbar --> <!-- Fixed navbar -->
<div class="navbar navbar-default navbar-fixed-top" role="navigation"> <div class="navbar navbar-default navbar-fixed-top" role="navigation">
<div class="container"> <div class="container">
@ -30,48 +32,36 @@
<span class="icon-bar"></span> <span class="icon-bar"></span>
</button> </button>
<img src="/static/clientside/bc.png" class="navbar-brand"> <img src="/static/clientside/bc.png" class="navbar-brand">
<a class="navbar-brand hidden-xs" href="{{ client_side_home }}">{% global_var 'COMPANY_NAME' %}</a>
</div> </div>
<div class="collapse navbar-collapse"> <div class="collapse navbar-collapse">
<ul class="nav navbar-nav"> <ul class="nav navbar-nav">
{% url 'client_side:home' as client_side_home %}
<li{% if client_side_home == request.path %} class="active"{% endif %}> <li{% if client_side_home == request.path %} class="active"{% endif %}>
<a href="{{ client_side_home }}">Личный кабинет</a>
<a href="{{ client_side_home }}">{% trans 'Personal account' %}</a>
</li> </li>
{% url 'client_side:pays' as client_side_pays %} {% url 'client_side:pays' as client_side_pays %}
<li{% if client_side_pays == request.path %} class="active"{% endif %}> <li{% if client_side_pays == request.path %} class="active"{% endif %}>
<a href="{{ client_side_pays }}">Платежи</a>
<a href="{{ client_side_pays }}">{% trans 'Pays' %}</a>
</li> </li>
{% url 'client_side:services' as client_side_services %} {% url 'client_side:services' as client_side_services %}
<li{% if client_side_services == request.path %} class="active"{% endif %}> <li{% if client_side_services == request.path %} class="active"{% endif %}>
<a href="{{ client_side_services }}">Услуги</a>
</li>
<!--<li>
<a href="#">
Сообщения из администрации
<span class="badge">14</span>
</a>
<a href="{{ client_side_services }}">{% trans 'Services' %}</a>
</li> </li>
<li><a href="#contact">График трафика</a></li>-->
<li class="dropdown"> <li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Другое <b class="caret"></b></a>
<a href="#" class="dropdown-toggle" data-toggle="dropdown">{% trans 'Other' %} <b class="caret"></b></a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<!--<li><a href="#">Подать заявку</a></li>-->
<li><a href="{% url 'client_side:debts' %}">Просмотреть и оплатить долги</a></li>
<!--<li><a href="#">Сеансы подключений</a></li>
<li class="divider"></li>
<li class="dropdown-header">Nav header</li>
<li><a href="#">Separated link</a></li>-->
<li><a href="{% url 'acc_app:logout' %}">Выйти</a></li>
<li><a href="{% url 'client_side:debts' %}">{% trans 'Show debts and pay it' %}</a></li>
<li><a href="{% url 'acc_app:logout' %}">{% trans 'Quit' %}</a></li>
</ul> </ul>
</li> </li>
</ul> </ul>
<span class="navbar-text">Ваш балланс <b>{{ subscriber.ballance|floatformat:2 }}</b> руб.</span>
<span class="navbar-text">
{% blocktrans with ballance=request.user.ballance|floatformat:2 %}Your balance is <b>{{ ballance }}</b>{% endblocktrans %} {% trans 'currency' %}
</span>
</div><!--/.nav-collapse --> </div><!--/.nav-collapse -->
</div> </div>
</div> </div>
@ -81,9 +71,10 @@
{% if request.user.is_staff %} {% if request.user.is_staff %}
<div class="alert alert-info"> <div class="alert alert-info">
<strong>Кстати.</strong>
Вы администратор, и не должны производить действия из кабинета пользователя, производите их из админки.
Кабинет клиента для вас только для ознакомления.
{% blocktrans trimmed %}
<strong>Attantion!</strong>
You are is admin, and do not be active here, please back to admin side. Client side to you for reference only.
{% endblocktrans %}
</div> </div>
{% endif %} {% endif %}
@ -95,7 +86,7 @@
<div id="footer"> <div id="footer">
<div class="container"> <div class="container">
<p class="text-muted"> <p class="text-muted">
Напишите разработчику <i>is-ttk@ya.ru</i>.
{% include 'clientsideapp/custom_pages/footer.htm' %}
</p> </p>
</div> </div>
</div> </div>

71
clientsideapp/templates/clientsideapp/index.html

@ -1,73 +1,4 @@
{% extends 'clientsideapp/ext.html' %} {% extends 'clientsideapp/ext.html' %}
{% block client_main %} {% block client_main %}
<div class="panel panel-primary">
<div class="panel-heading">Наши реквизиты</div>
<div class="panel-body">
<ul>
<li><b>Режим работы:</b> с 9:00 до 22:00</li>
<li><b>Телефоны:</b> +79788328885, +79788318999</li>
<li><b>Адрес:</b> пос. Нижнегорский, ул. Победы 38. 2й этаж старого дома быта, возле рынка</li>
</ul>
<script type="text/javascript" charset="utf-8" async src="https://api-maps.yandex.ru/services/constructor/1.0/js/?um=constructor%3A62221c38a606ad870f2cabc9fd476c8ae1a532993915b73553693f7fb5c7de68&amp;width=100%25&amp;height=654&amp;lang=ru_RU&amp;scroll=true"></script>
</div>
</div>
<div class="page-header">
<h3 class="page-header">Тарифы на дополнительные услуги</h3>
<!--Скачать <a href="/static/clientside/ISPlaylist.m3u">IPTV список каналов</a>, кодировка UTF-8</p>-->
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>Услуга</th>
<th>Стоимость (руб)</th>
</tr>
</thead>
<tbody>
<tr>
<td>Выезд мастера. Цена зависит от удалённости от Нижнегорска</td>
<td>100-200</td>
</tr>
<tr>
<td>Настройка роутера</td>
<td>300</td>
</tr>
<tr>
<td>Установка и восстановление сетевых настроек</td>
<td>100</td>
</tr>
<tr>
<td>Замена кабеля (витая пара)</td>
<td>30 руб/метр</td>
</tr>
<tr>
<td>Замена конектора <a href="/static/clientside/rj45.png" target="_blank">RJ-45</a></td>
<td>50</td>
</tr>
<tr>
<td>Роутер <a href="/static/clientside/TL-WR840N-v2.jpg" target="_blank">TL-WR840N</a> + настройка</td>
<td>1800</td>
</tr>
<tr>
<td>Установка драйвера (с настройкой сетевых установок)</td>
<td>100</td>
</tr>
<tr>
<td>"Что-то не работает" &#8211; по договорённости</td>
<td>~</td>
</tr>
<tr>
<td>Замена блока питания</td>
<td>200</td>
</tr>
<tr>
<td>Замена оптоволоконных линий связи</td>
<td>от 500</td>
</tr>
</tbody>
</table>
</div>
{% include 'clientsideapp/custom_pages/main_page.htm' %}
{% endblock %} {% endblock %}

7
clientsideapp/templates/clientsideapp/modal_service_buy.html

@ -6,16 +6,17 @@
</div> </div>
<div class="modal-body"> <div class="modal-body">
<h3>{% trans 'Are you sure you want to order the service?' %}</h3> <h3>{% trans 'Are you sure you want to order the service?' %}</h3>
<p>Будте внимательны, после заказа услуги с вашего счёта <b>снимутся средства</b>, и вы сможете пользоваться купленной услугой.</p>
<p>{% trans 'Be careful, after purchasing the service you will <b>withdraw money</b>, and you will be able to use the purchased service.' %}</p>
<h3>{{ service.title }}</h3> <h3>{{ service.title }}</h3>
<p>{{ service.descr }}</p> <p>{{ service.descr }}</p>
<!--<p>{% blocktrans with speedIn=service.speedIn speedOut=service.speedOut amount=service.amount %}Inbound speed: {{ speedIn }} MBit/s<br>
<!--<p>{# {% blocktrans trimmed with speedIn=service.speedIn speedOut=service.speedOut amount=service.amount %}
Inbound speed: {{ speedIn }} MBit/s<br>
Outgoing speed: {{ speedOut }} MBit/s<br> Outgoing speed: {{ speedOut }} MBit/s<br>
Cost: {{ amount }} rubles.{% endblocktrans %}</p>--> Cost: {{ amount }} rubles.{% endblocktrans %}</p>-->
<p>Стоимость {{ service.amount }} руб.</p>
<p>{% blocktrans with amount=service.amount|floatformat:2 %}The cost is {{ amount }}{% endblocktrans %} {% trans 'currency' %}</p>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="submit" class="btn btn-sm btn-primary"> <button type="submit" class="btn btn-sm btn-primary">

44
clientsideapp/templates/clientsideapp/services.html

@ -8,38 +8,42 @@
<div class="row"> <div class="row">
<div class="col-lg-7"> <div class="col-lg-7">
{% if current_service %} {% if current_service %}
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
Ваша текущая услуга
{% trans 'Your current service' %}
</div> </div>
<div class="panel-body"> <div class="panel-body">
<h3 class="panel-title">{{ current_service.tariff.title }}</h3><br> <h3 class="panel-title">{{ current_service.tariff.title }}</h3><br>
<dl class="dl-horizontal"> <dl class="dl-horizontal">
<dt>Дата подключения</dt>
<dt>{% trans 'The date of connection' %}</dt>
<dd>{{ current_service.time_start|date:"d E Y, l" }}</dd> <dd>{{ current_service.time_start|date:"d E Y, l" }}</dd>
<dt>Время завершения услуги</dt>
<dt>{% trans 'The date of finish service' %}</dt>
<dd>{{ current_service.deadline|date:"d E Y, l" }}</dd> <dd>{{ current_service.deadline|date:"d E Y, l" }}</dd>
<dt>Стоимость</dt>
<dt>{% trans 'Cost' %}</dt>
<dd>{{ current_service.tariff.amount }} {% trans 'currency' %}</dd> <dd>{{ current_service.tariff.amount }} {% trans 'currency' %}</dd>
</dl> </dl>
<p>{{ current_service.tariff.descr }}</p> <p>{{ current_service.tariff.descr }}</p>
</div> </div>
</div> </div>
{% else %} {% else %}
<div class="alert alert-warning" role="alert"> <div class="alert alert-warning" role="alert">
<span class="glyphicon glyphicon-exclamation-sign"></span> <span class="glyphicon glyphicon-exclamation-sign"></span>
<strong>Внимание!</strong> У вас нет услуги, для использования ресурсов приобретите нужную услугу из представленных тут.
{% blocktrans trimmed %}
<strong>Attantion!</strong>
You have not yet a service, for use the services please purchase service you want.
{% endblocktrans %}
</div> </div>
{% endif %} {% endif %}
</div> </div>
<div class="col-lg-5"> <div class="col-lg-5">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
Доступные для заказа услуги
{% trans 'Services available for ordering' %}
</div> </div>
<div class="panel-body"> <div class="panel-body">
<div class="container-fluid"> <div class="container-fluid">
@ -48,7 +52,7 @@
<div class="col-lg-12"> <div class="col-lg-12">
<h3>{{ tarif.title }}</h3> <h3>{{ tarif.title }}</h3>
<i>{{ tarif.amount }} руб.</i>
<i>{{ tarif.amount }} {% trans 'currency' %}</i>
<p>{{ tarif.descr }}</p> <p>{{ tarif.descr }}</p>
{% if request.user.is_staff or current_service %} {% if request.user.is_staff or current_service %}
@ -61,7 +65,7 @@
</div> </div>
{% empty %} {% empty %}
<div class="col-lg-4"> <div class="col-lg-4">
<h3 class="panel-title">Нет доступных услуг для заказа</h3>
<h3 class="panel-title">{% trans 'No services available for ordering' %}</h3>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
@ -70,28 +74,10 @@
</div> </div>
</div> </div>
</div> </div>
{% include 'clientsideapp/services_ext.txt' %}
{% include 'clientsideapp/custom_pages/service.htm' with active_service=current_service %}
</div> </div>
</div> </div>
{% include 'clientsideapp/custom_pages/service_bottom.htm' %}
<div class="row">
<div class="col-sm-12">
<div class="panel panel-info">
<div class="panel-heading">
<span class="glyphicon glyphicon-info-sign"></span>
Как работает личный кабинет, раздел услуг
</div>
<div class="panel-body">
<p>Перед вами находится 2 столбца, в левом ваша текущая подключённая услуга, а в правом доступные для заказа услуги.
Когда у вас уже есть подключённая услуга то заказать новую вы не можете пока не завершится текущая.</p>
<p>Когда у вас нет действующей подключённой услуги то кнопка заказа станет активной, и вы сможете заказать для себя услугу.
Обратите внимание что именно в этот момент с вашего счёта снимутся деньги в соответствии со стоимостью услуги.
</p>
</div>
</div>
</div>
</div>
{% endblock %} {% endblock %}

12
clientsideapp/views.py

@ -28,7 +28,7 @@ def pays(request):
@login_required @login_required
def services(request): def services(request):
try: try:
abon = Abon.objects.get(pk=request.user.pk)
abon = request.user
all_tarifs = Tariff.objects.get_tariffs_by_group(abon.group.pk) all_tarifs = Tariff.objects.get_tariffs_by_group(abon.group.pk)
current_service = abon.active_tariff() current_service = abon.active_tariff()
except Abon.DoesNotExist: except Abon.DoesNotExist:
@ -43,14 +43,14 @@ def services(request):
@login_required @login_required
@transaction.atomic @transaction.atomic
def buy_service(request, srv_id): 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) service = get_object_or_404(Tariff, pk=srv_id)
try: try:
current_service = abon.active_tariff() current_service = abon.active_tariff()
if request.method == 'POST': 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) % 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) messages.success(request, _("The service '%s' wan successfully activated") % service.title)
else: else:
return render_to_text('clientsideapp/modal_service_buy.html', { return render_to_text('clientsideapp/modal_service_buy.html', {
@ -76,7 +76,7 @@ def debts_list(request):
@transaction.atomic @transaction.atomic
def debt_buy(request, d_id): def debt_buy(request, d_id):
debt = get_object_or_404(InvoiceForPayment, id=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': if request.method == 'POST':
try: try:
sure = request.POST.get('sure') sure = request.POST.get('sure')
@ -86,7 +86,7 @@ def debt_buy(request, d_id):
raise LogicError(_('Your account have not enough money')) raise LogicError(_('Your account have not enough money'))
amount = -debt.amount 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(), 'username': abon.get_full_name(),
'amount': amount 'amount': amount
}) })

21
devapp/base_intr.py

@ -1,8 +1,11 @@
# -*- coding: utf-8 -*-
from abc import ABCMeta, abstractmethod from abc import ABCMeta, abstractmethod
from easysnmp import Session from easysnmp import Session
class DeviceImplementationError(Exception):
pass
class DevBase(object, metaclass=ABCMeta): class DevBase(object, metaclass=ABCMeta):
def __init__(self, dev_instance=None): def __init__(self, dev_instance=None):
@ -10,19 +13,19 @@ class DevBase(object, metaclass=ABCMeta):
@staticmethod @staticmethod
def description(): def description():
"""Возвращает текстовое описание"""
pass
@abstractmethod @abstractmethod
def reboot(self): def reboot(self):
"""Перезагружает устройство"""
pass
@abstractmethod @abstractmethod
def get_ports(self): def get_ports(self):
"""Получаем инфу о портах"""
pass
@abstractmethod @abstractmethod
def get_device_name(self): def get_device_name(self):
"""Получаем имя устройства по snmp"""
"""Return device name by snmp"""
@abstractmethod @abstractmethod
def uptime(self): def uptime(self):
@ -30,17 +33,17 @@ class DevBase(object, metaclass=ABCMeta):
@abstractmethod @abstractmethod
def get_template_name(self): def get_template_name(self):
"""Получаем путь к html шаблону отображения устройства"""
"""Return path to html template for device"""
@staticmethod @staticmethod
@abstractmethod @abstractmethod
def has_attachable_to_subscriber(): def has_attachable_to_subscriber():
"""Можно-ли подключать устройство к абоненту"""
"""Can connect device to subscriber"""
@staticmethod @staticmethod
@abstractmethod @abstractmethod
def is_use_device_port(): def is_use_device_port():
"""True если при авторизации по opt82 используется порт"""
"""True if used device port while opt82 authorization"""
class BasePort(object, metaclass=ABCMeta): class BasePort(object, metaclass=ABCMeta):
@ -70,7 +73,7 @@ class SNMPBaseWorker(object, metaclass=ABCMeta):
self.ses = Session(hostname=ip, community=community, version=ver) self.ses = Session(hostname=ip, community=community, version=ver)
def set_int_value(self, oid, value): 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): def get_list(self, oid):
for v in self.ses.walk(oid): for v in self.ses.walk(oid):

14
devapp/dev_types.py

@ -3,7 +3,7 @@ from django.utils.translation import gettext_lazy as _
from mydefs import RuTimedelta, safe_int from mydefs import RuTimedelta, safe_int
from datetime import timedelta from datetime import timedelta
from easysnmp import EasySNMPTimeoutError from easysnmp import EasySNMPTimeoutError
from .base_intr import DevBase, SNMPBaseWorker, BasePort
from .base_intr import DevBase, SNMPBaseWorker, BasePort, DeviceImplementationError
class DLinkPort(BasePort): 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') return self.get_item('.1.3.6.1.4.1.2021.8.1.101.1')
def get_ports(self): 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')) 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')) 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')) 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 = [] res = []
for n, speed in enumerate(speeds):
try:
for n in range(interfaces_count):
status = True if int(stats[n]) == 1 else False status = True if int(stats[n]) == 1 else False
res.append(DLinkPort( res.append(DLinkPort(
n+1, n+1,
nams[n] if len(nams) > 0 else _('does not fetch the name'),
nams[n] if len(nams) > 0 else '',
status, status,
macs[n] if len(macs) > 0 else _('does not fetch the mac'), macs[n] if len(macs) > 0 else _('does not fetch the mac'),
int(speed or 0),
int(speeds[n]) if len(speeds) > 0 else 0,
self)) self))
return res return res
except IndexError:
raise DeviceImplementationError('Dlink port index error')
def get_device_name(self): def get_device_name(self):
return self.get_item('.1.3.6.1.2.1.1.1.0') return self.get_item('.1.3.6.1.2.1.1.1.0')

5
devapp/models.py

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import requests import requests
from django.db import models
from django.db import models, ProgrammingError
from djing.fields import MACAddressField from djing.fields import MACAddressField
from .base_intr import DevBase from .base_intr import DevBase
from mydefs import MyGenericIPAddressField, MyChoicesAdapter, ip2int from mydefs import MyGenericIPAddressField, MyChoicesAdapter, ip2int
@ -76,7 +76,7 @@ class Device(models.Model):
) )
verbose_name = _('Device') verbose_name = _('Device')
verbose_name_plural = _('Devices') verbose_name_plural = _('Devices')
ordering = ['comment']
ordering = ['id']
def get_abons(self): def get_abons(self):
pass pass
@ -107,6 +107,7 @@ class Device(models.Model):
def update_dhcp(self): def update_dhcp(self):
if self.devtype not in ('On','Dl'): if self.devtype not in ('On','Dl'):
return return
#raise ProgrammingError('переделать это безобразие')
# FIXME: переделать это безобразие # FIXME: переделать это безобразие
grp = self.user_group.id grp = self.user_group.id
code = '' code = ''

2
devapp/onu_register.sh

@ -29,6 +29,6 @@ if grep "${MAC}" "${DHCP_PATH}/${PART_CODE}.conf" > /dev/null; then
else else
# add new mac # add new mac
echo "subclass \"${PART_CODE}\" \"${MAC}\";" >> "${DHCP_PATH}/${PART_CODE}.conf" 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 fi

8
devapp/templates/devapp/custom_dev_page/ports.html

@ -18,16 +18,16 @@
{% for port in ports %} {% for port in ports %}
{% if port.st %} {% if port.st %}
{% if port.sp == 10 %}
{% if port.sp == 10000000 %}
<div class="port kilo text-center"> <div class="port kilo text-center">
<b>10 mbps</b> <b>10 mbps</b>
{% elif port.sp == 100 %}
{% elif port.sp == 100000000 %}
<div class="port mega text-center"> <div class="port mega text-center">
<b>100 mbps</b> <b>100 mbps</b>
{% elif port.sp == 1000 %}
{% elif port.sp == 1000000000 %}
<div class="port giga text-center"> <div class="port giga text-center">
<b>1 gbps</b> <b>1 gbps</b>
{% elif port.sp == 10000 %}
{% elif port.sp == 10000000000 %}
<div class="port ten text-center"> <div class="port ten text-center">
<b>10 gbps</b> <b>10 gbps</b>
{% else %} {% else %}

2
devapp/templates/devapp/devices.html

@ -41,7 +41,7 @@
</thead> </thead>
<tbody> <tbody>
{% 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 %} {% for dev in devices %}
<tr> <tr>

23
devapp/views.py

@ -13,6 +13,7 @@ from django.utils.translation import gettext_lazy as _, gettext
from easysnmp import EasySNMPTimeoutError, EasySNMPError from easysnmp import EasySNMPTimeoutError, EasySNMPError
from django.views.generic import ListView, DetailView 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 mydefs import res_success, res_error, only_admins, ping, ip_addr_regex
from abonapp.models import Abon from abonapp.models import Abon
from group_app.models import Group 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.telebot import send_notify
from chatbot.models import ChatException from chatbot.models import ChatException
from jsonview.decorators import json_view from jsonview.decorators import json_view
from djing.global_base_views import HashAuthView, AllowedSubnetMixin, OrderingMixin
from djing import global_base_views
from .models import Device, Port, DeviceDBException, DeviceMonitoringException from .models import Device, Port, DeviceDBException, DeviceMonitoringException
from .forms import DeviceForm, PortForm from .forms import DeviceForm, PortForm
from mydefs import safe_int from mydefs import safe_int
@ -35,7 +36,7 @@ class BaseDeviceListView(ListView):
@method_decorator([login_required, only_admins], name='dispatch') @method_decorator([login_required, only_admins], name='dispatch')
class DevicesListView(BaseDeviceListView, OrderingMixin):
class DevicesListView(BaseDeviceListView, global_base_views.OrderingMixin):
context_object_name = 'devices' context_object_name = 'devices'
template_name = 'devapp/devices.html' template_name = 'devapp/devices.html'
@ -62,7 +63,7 @@ class DevicesListView(BaseDeviceListView, OrderingMixin):
@method_decorator([login_required, only_admins], name='dispatch') @method_decorator([login_required, only_admins], name='dispatch')
class DevicesWithoutGroupsListView(BaseDeviceListView, OrderingMixin):
class DevicesWithoutGroupsListView(BaseDeviceListView, global_base_views.OrderingMixin):
context_object_name = 'devices' context_object_name = 'devices'
template_name = 'devapp/devices_null_group.html' template_name = 'devapp/devices_null_group.html'
queryset = Device.objects.filter(group=None).only('comment', 'devtype', 'pk', 'ip_address') 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') @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' template_name = 'devapp/manage_ports/modal_show_subscriber_on_port.html'
http_method_names = ['get'] http_method_names = ['get']
@ -190,6 +191,14 @@ class ShowSubscriberOnPort(DetailView):
obj = Abon.objects.get(device_id=dev_id, dev_port_id=port_id) obj = Abon.objects.get(device_id=dev_id, dev_port_id=port_id)
except Abon.DoesNotExist: except Abon.DoesNotExist:
raise Http404(gettext('Subscribers on port does not exist')) 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 return obj
@ -370,7 +379,7 @@ def devview(request, device_id):
}) })
except EasySNMPError: except EasySNMPError:
messages.error(request, _('SNMP error on device')) messages.error(request, _('SNMP error on device'))
except DeviceDBException as e:
except (DeviceDBException, DeviceImplementationError) as e:
messages.error(request, e) messages.error(request, e)
return render(request, 'devapp/custom_dev_page/' + template_name, { return render(request, 'devapp/custom_dev_page/' + template_name, {
'dev': dev 'dev': dev
@ -399,7 +408,7 @@ def toggle_port(request, device_id, portid, status=0):
except EasySNMPTimeoutError: except EasySNMPTimeoutError:
messages.error(request, _('wait for a reply from the SNMP Timeout')) messages.error(request, _('wait for a reply from the SNMP Timeout'))
except EasySNMPError as e: 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) return redirect('devapp:view', dev.group.pk if dev.group is not None else 0, device_id)
@ -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 # Api view for monitoring devices
# #

33
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

69
djing/global_base_views.py

@ -1,14 +1,27 @@
from hashlib import sha256 from hashlib import sha256
from json import dumps
from django.views.generic.base import View 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.conf import settings
from django.views.generic import ListView
from netaddr import IPNetwork, IPAddress from netaddr import IPNetwork, IPAddress
from django.core.paginator import InvalidPage, EmptyPage
API_AUTH_SECRET = getattr(settings, 'API_AUTH_SECRET') API_AUTH_SECRET = getattr(settings, 'API_AUTH_SECRET')
API_AUTH_SUBNET = getattr(settings, 'API_AUTH_SUBNET') 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): class HashAuthView(View):
@staticmethod @staticmethod
@ -87,4 +100,54 @@ class OrderingMixin(object):
if direction == 'down': if direction == 'down':
dfx = '-' dfx = '-'
if order_by: 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)
})

2
djing/local_settings.py.template

@ -57,3 +57,5 @@ API_AUTH_SECRET = 'your api secret'
# Allowed subnet for api # Allowed subnet for api
API_AUTH_SUBNET = '127.0.0.0/8' API_AUTH_SUBNET = '127.0.0.0/8'
# Company name
COMPANY_NAME = 'Your company name'

16
djing/settings.py

@ -19,13 +19,14 @@ from django.urls import reverse_lazy
SECRET_KEY = local_settings.SECRET_KEY SECRET_KEY = local_settings.SECRET_KEY
# SECURITY WARNING: don't run with debug turned on in production! # 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 ALLOWED_HOSTS = local_settings.ALLOWED_HOSTS
# required for django-guardian # required for django-guardian
AUTHENTICATION_BACKENDS = ( AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend', # default
'djing.auth_backends.CustomAuthBackend',
#'django.contrib.auth.backends.ModelBackend', # default
'guardian.backends.ObjectPermissionBackend' 'guardian.backends.ObjectPermissionBackend'
) )
@ -78,14 +79,16 @@ TEMPLATES = [
'APP_DIRS': True, 'APP_DIRS': True,
'OPTIONS': { 'OPTIONS': {
'context_processors': [ 'context_processors': [
'django.template.context_processors.debug',
#'django.template.context_processors.debug',
'django.template.context_processors.request', 'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth', 'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages', 'django.contrib.messages.context_processors.messages',
'taskapp.context_proc.get_active_tasks_count', 'taskapp.context_proc.get_active_tasks_count',
'global_context_processors.context_processor_additional_profile',
'msg_app.context_processors.get_new_messages_count' '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 SESSION_COOKIE_HTTPONLY = True
@ -187,3 +190,6 @@ API_AUTH_SECRET = local_settings.API_AUTH_SECRET
# Allowed subnet for api # Allowed subnet for api
API_AUTH_SUBNET = local_settings.API_AUTH_SUBNET API_AUTH_SUBNET = local_settings.API_AUTH_SUBNET
# Company name
COMPANY_NAME = local_settings.COMPANY_NAME

0
djing/templatetags/__init__.py

9
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, '')

2
docs/views.md

@ -33,7 +33,7 @@ class PaysListView(ListView):
На примере *devapp/devices.html* можно рассмотреть такую сортировку. На примере *devapp/devices.html* можно рассмотреть такую сортировку.
Там указан url с параметрами Там указан 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*, со знаком -. То есть эквивалент будет выглядеть примерно так: Тут обратная сортировка по полю *ip_address*, со знаком -. То есть эквивалент будет выглядеть примерно так:
> Device.objects.all().order_by('-ip_address') > Device.objects.all().order_by('-ip_address')

12
global_context_processors.py

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

2
install_debian.sh

@ -1,3 +1,3 @@
/usr/bin/bash - /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

3
mapapp/templates/maps/dot.html

@ -1,6 +1,7 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% load i18n %} {% load i18n %}
{% load bootstrap3 %} {% load bootstrap3 %}
{% load globaltags %}
{% block main %} {% block main %}
<ol class="breadcrumb"> <ol class="breadcrumb">
@ -20,7 +21,7 @@
<div class="row"> <div class="row">
<div class="{% if dot.id %}col-sm-6{% else %}col-sm-12{% endif %}"> <div class="{% if dot.id %}col-sm-6{% else %}col-sm-12{% endif %}">
<form role="form" action="{{ form_url }}" method="post" enctype="multipart/form-data">{% csrf_token %} <form role="form" action="{{ form_url }}" method="post" enctype="multipart/form-data">{% csrf_token %}
<input type="hidden" name="MAX_FILE_SIZE" value="{{ FILE_UPLOAD_MAX_MEMORY_SIZE }}"/>
<input type="hidden" name="MAX_FILE_SIZE" value="{% global_var 'FILE_UPLOAD_MAX_MEMORY_SIZE' %}"/>
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<h3 class="panel-title">{% trans 'Map point' %}</h3> <h3 class="panel-title">{% trans 'Map point' %}</h3>

4
mapapp/templates/maps/modal_add_dot.html

@ -1,6 +1,6 @@
{% load i18n %}{% load bootstrap3 %}
{% load i18n %}{% load bootstrap3 %}{% load globaltags %}
<form action="{% url 'mapapp:modal_add_dot' %}" class="form-ajax" method="post" enctype="multipart/form-data">{% csrf_token %} <form action="{% url 'mapapp:modal_add_dot' %}" class="form-ajax" method="post" enctype="multipart/form-data">{% csrf_token %}
<input type="hidden" name="MAX_FILE_SIZE" value="{{ FILE_UPLOAD_MAX_MEMORY_SIZE }}"/>
<input type="hidden" name="MAX_FILE_SIZE" value="{% global_var 'FILE_UPLOAD_MAX_MEMORY_SIZE' %}"/>
<div class="modal-header primary"> <div class="modal-header primary">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button> <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title"><span class="glyphicon glyphicon-warning-sign"></span>{% trans 'Add point' %}</h4> <h4 class="modal-title"><span class="glyphicon glyphicon-warning-sign"></span>{% trans 'Add point' %}</h4>

359
migrate_to_0.2.py

@ -0,0 +1,359 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import sys
import shutil
from json import dump
import django
'''
Some permissions is not migrates, all admins is superuser
after migrate.
'''
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djing.settings")
from django.db.models import fields as django_fields
def get_fixture_from_unchanget_model(model_name: str, model_class):
"""
Создаёт фикстуру если модели между версиями не изменились
:param model_name: str 'app_label.model_name'
:param model_class: Model модель для которой надо сделать фикстуру
:return: список словарей
"""
print(model_name)
def get_fields(obj):
fields = dict()
for field in obj._meta.get_fields():
if isinstance(field, (django_fields.reverse_related.ManyToOneRel, django_fields.reverse_related.ManyToManyRel)):
continue
field_val = getattr(obj, field.name)
if field_val is None:
continue
if isinstance(field, django_fields.related.ManyToManyField):
fields[field.name] = [f.pk for f in field_val.all()]
elif isinstance(field, (django_fields.related.ForeignKey, django.contrib.contenttypes.fields.GenericForeignKey)):
fields[field.name] = field_val.pk
elif isinstance(field, django_fields.FloatField):
fields[field.name] = float(field_val)
elif isinstance(field, django_fields.DateTimeField):
fields[field.name] = str(field_val)
elif isinstance(field, django_fields.AutoField):
continue
else:
fields[field.name] = field_val
return fields
res = [{
'model': model_name,
'pk': obj.pk,
'fields': get_fields(obj)
} for obj in model_class.objects.all()]
return res
def dump_groups():
from abonapp import models
print('group_app.group')
res = [{
'model': 'group_app.group',
'pk': abon_group.pk,
'fields': {
'title': abon_group.title
}
} for abon_group in models.AbonGroup.objects.all()]
return res
def dump_abonapp():
from abonapp import models
res = get_fixture_from_unchanget_model('abonapp.abontariff', models.AbonTariff)
res += get_fixture_from_unchanget_model('abonapp.abonstreet', models.AbonStreet)
res += get_fixture_from_unchanget_model('abonapp.extrafieldsmodel', models.ExtraFieldsModel)
# res += get_fixture_from_unchanget_model('abonapp.abonlog', models.AbonLog)
print('abonapp.abon')
res += [{
'model': 'abonapp.abon',
'pk': abon.pk,
'fields': {
'current_tariff': abon.current_tariff.pk if abon.current_tariff else None,
'group': abon.group.pk if abon.group else None,
'ballance': abon.ballance,
'ip_address': abon.ip_address,
'description': abon.description,
'street': abon.street.pk if abon.street else None,
'house': abon.house,
'extra_fields': [a.pk for a in abon.extra_fields.all()],
'device': abon.device.pk if abon.device else None,
'dev_port': abon.dev_port if abon.dev_port else None,
'is_dynamic_ip': abon.is_dynamic_ip,
'markers': abon.markers
}
} for abon in models.Abon.objects.filter(is_admin=False)]
res += get_fixture_from_unchanget_model('abonapp.passportinfo', models.PassportInfo)
res += get_fixture_from_unchanget_model('abonapp.invoiceforpayment', models.InvoiceForPayment)
res += get_fixture_from_unchanget_model('abonapp.alltimepaylog', models.AllTimePayLog)
res += get_fixture_from_unchanget_model('abonapp.abonrawpassword', models.AbonRawPassword)
res += get_fixture_from_unchanget_model('abonapp.additionaltelephone', models.AdditionalTelephone)
res += get_fixture_from_unchanget_model('abonapp.periodicpayforid', models.PeriodicPayForId)
return res
def dump_tariffs():
from tariff_app import models
from abonapp.models import AbonGroup
print('tariff_app.tariff')
res = [{
'model': 'tariff_app.tariff',
'pk': trf.pk,
'fields': {
'title': trf.title,
'descr': trf.descr,
'speedIn': trf.speedIn,
'speedOut': trf.speedOut,
'amount': trf.amount,
'calc_type': trf.calc_type,
'is_admin': trf.is_admin,
'groups': [ag.pk for ag in AbonGroup.objects.filter(tariffs__in=[trf])]
}
} for trf in models.Tariff.objects.all()]
res += get_fixture_from_unchanget_model('tariff_app.periodicpay', models.PeriodicPay)
return res
def dump_devs():
from devapp import models
print('devapp.device')
res = [{
'model': 'devapp.device',
'pk': dv.pk,
'fields': {
'ip_address': dv.ip_address,
'mac_addr': str(dv.mac_addr) if dv.mac_addr else None,
'comment': dv.comment,
'devtype': dv.devtype,
'man_passw': dv.man_passw,
'group': dv.user_group.pk if dv.user_group else None,
'parent_dev': dv.parent_dev.pk if dv.parent_dev else None,
'snmp_item_num': dv.snmp_item_num,
'status': dv.status,
'is_noticeable': dv.is_noticeable
}
} for dv in models.Device.objects.all()]
res += get_fixture_from_unchanget_model('devapp.port', models.Port)
return res
def dump_accounts():
from accounts_app import models
from abonapp.models import AbonGroup
def get_responsibility_groups(account):
responsibility_groups = AbonGroup.objects.filter(profiles__in=[account])
ids = [ag.pk for ag in responsibility_groups]
return ids
print('accounts_app.baseaccount')
res = [{
'model': 'accounts_app.baseaccount',
'pk': up.pk,
'fields': {
'username': up.username,
'fio': up.fio,
'birth_day': up.birth_day,
'is_active': up.is_active,
'is_admin': up.is_admin,
'telephone': up.telephone,
'password': up.password,
'last_login': up.last_login,
'is_superuser': up.is_admin
}
} for up in models.UserProfile.objects.all()]
print('accounts_app.userprofile')
res += [{
'model': 'accounts_app.userprofile',
'pk': up.pk,
'fields': {
'avatar': up.avatar.pk if up.avatar else None,
'email': up.email,
'responsibility_groups': get_responsibility_groups(up)
}
} for up in models.UserProfile.objects.filter(is_admin=True)]
return res
def dump_photos():
from photo_app.models import Photo
print('photo_app.photo')
res = [{
'model': 'photo_app.photo',
'pk': p.pk,
'fields': {
'image': "%s" % p.image,
'wdth': p.wdth,
'heigt': p.heigt
}
} for p in Photo.objects.all()]
return res
def dump_chatbot():
from chatbot import models
res = get_fixture_from_unchanget_model('chatbot.telegrambot', models.TelegramBot)
res += get_fixture_from_unchanget_model('chatbot.messagehistory', models.MessageHistory)
res += get_fixture_from_unchanget_model('chatbot.messagequeue', models.MessageQueue)
return res
def dump_map():
from mapapp import models
res = get_fixture_from_unchanget_model('mapapp.dot', models.Dot)
return res
def dump_task_app():
from taskapp import models
res = get_fixture_from_unchanget_model('taskapp.changelog', models.ChangeLog)
res += get_fixture_from_unchanget_model('taskapp.task', models.Task)
res += get_fixture_from_unchanget_model('taskapp.ExtraComment', models.ExtraComment)
return res
def dump_auth():
from django.contrib.auth import models
from django.contrib.contenttypes.models import ContentType
res = get_fixture_from_unchanget_model('contenttypes.contenttype', ContentType)
res += get_fixture_from_unchanget_model('auth.group', models.Group)
res += get_fixture_from_unchanget_model('auth.permission', models.Permission)
return res
def dump_guardian():
from guardian import models
print('guardian.groupobjectpermission')
res = [{
'model': 'guardian.groupobjectpermission',
'pk': gp.pk,
'fields': {
'group': gp.group.pk,
'permission': gp.permission.pk,
'content_type': gp.content_type.pk,
'object_pk': str(gp.object_pk),
'content_object': gp.content_object.pk
}
} for gp in models.GroupObjectPermission.objects.all()]
print('guardian.userobjectpermission')
res += [{
'model': 'guardian.userobjectpermission',
'pk': up.pk,
'fields': {
'permission': up.permission.pk,
'content_type': up.content_type.pk,
'object_pk': str(up.object_pk),
'user': up.user.pk
}
} for up in models.UserObjectPermission.objects.all()]
return res
def make_migration():
from datetime import datetime, date
def my_date_converter(o):
if isinstance(o, datetime) or isinstance(o, date):
return "%s" % o
def appdump(path, func):
fname = os.path.join(*path)
path_dir = os.path.join(*path[:-1])
if not os.path.isdir(path_dir):
os.mkdir(path_dir)
with open(fname, 'w') as f:
dump(func(), f, default=my_date_converter, ensure_ascii=False)
if not os.path.isdir('fixtures'):
os.mkdir('fixtures')
appdump(['fixtures', 'group_app', 'groups_fixture.json'], dump_groups)
appdump(['fixtures', 'tariff_app', 'tariffs_fixture.json'], dump_tariffs)
appdump(['fixtures', 'photo_app', 'photos_fixture.json'], dump_photos)
appdump(['fixtures', 'devapp', 'devs_fixture.json'], dump_devs)
appdump(['fixtures', 'accounts_app', 'accounts_fixture.json'], dump_accounts)
appdump(['fixtures', 'abonapp', 'abon_fixture.json'], dump_abonapp)
appdump(['fixtures', 'chatbot', 'chatbot_fixture.json'], dump_chatbot)
appdump(['fixtures', 'mapapp', 'map_fixture.json'], dump_map)
appdump(['fixtures', 'taskapp', 'task_fixture.json'], dump_task_app)
# appdump(['fixtures', 'accounts_app', 'auth_fixture.json'], dump_auth)
# appdump(['fixtures', 'accounts_app', 'guardian_fixture.json'], dump_guardian)
def move_to_fixtures_dirs():
fixdir = 'fixtures'
for dr in os.listdir(fixdir):
fixture_dir = os.path.join(fixdir, dr)
for fixture_file in os.listdir(fixture_dir):
from_file = os.path.join(fixture_dir, fixture_file)
dst_dir = os.path.join(dr, fixdir)
to_file = os.path.join(dst_dir, fixture_file)
if not os.path.isdir(dst_dir):
os.mkdir(dst_dir)
shutil.copyfile(from_file, to_file)
print('cp %s -> %s' % (from_file, to_file))
def apply_fixtures():
from django.core.management import execute_from_command_line
from accounts_app.models import UserProfile
#from django.contrib.auth import models
UserProfile.objects.filter(username='AnonymousUser').delete()
#print('clearing auth.group')
#models.Group.objects.all().delete()
#print('clearing auth.permission')
#models.Permission.objects.all().delete()
fixtures_names = [
'groups_fixture.json', 'tariffs_fixture.json', 'photos_fixture.json',
'devs_fixture.json', 'accounts_fixture.json', 'abon_fixture.json',
'chatbot_fixture.json', 'map_fixture.json', 'task_fixture.json'
]
# 'auth_fixture.json', 'guardian_fixture.json'
print('./manage.py loaddata ' + ', '.join(fixtures_names))
execute_from_command_line([sys.argv[0], 'loaddata'] + fixtures_names)
if __name__ == '__main__':
if len(sys.argv) < 2:
print('Usage: ./migrate_to_0.2.py [makedump OR applydump]')
exit(1)
choice = sys.argv[1]
if choice == 'applydump':
django.setup()
move_to_fixtures_dirs()
apply_fixtures()
shutil.rmtree('fixtures')
elif choice == 'makedump':
django.setup()
make_migration()
else:
print('Unexpected choice')

6
msg_app/models.py

@ -124,8 +124,10 @@ class ConversationManager(models.Manager):
return conversation return conversation
def get_new_messages_count(self, account): def get_new_messages_count(self, account):
ms_count = MessageStatus.objects.filter(user=account, state='new').count()
return ms_count
if isinstance(account, UserProfile):
return MessageStatus.objects.filter(user=account, state='new').count()
else:
return 0
def fetch(self, account): def fetch(self, account):
conversations = self.filter(models.Q(author=account) | models.Q(participants__in=[account])).annotate( conversations = self.filter(models.Q(author=account) | models.Q(participants__in=[account])).annotate(

6
queue_mngr.py

@ -4,6 +4,8 @@ from pickle import loads
from pid.decorator import pidfile from pid.decorator import pidfile
import socket import socket
import django import django
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djing.settings")
from mydefs import LogicError
''' '''
@ -18,6 +20,7 @@ obj = {
def on_new_data(client_sock, ip): def on_new_data(client_sock, ip):
try:
data = client_sock.recv(16384) data = client_sock.recv(16384)
data = loads(data) data = loads(data)
action = data['cmd'] action = data['cmd']
@ -30,6 +33,9 @@ def on_new_data(client_sock, ip):
dhcp_expiry(data['client_ip']) dhcp_expiry(data['client_ip'])
elif action == 'release': elif action == 'release':
dhcp_release(data['client_ip']) dhcp_release(data['client_ip'])
except LogicError as e:
print('LogicError', e)
finally:
client_sock.close() client_sock.close()

BIN
static/clientside/bc.png

Before

Width: 80  |  Height: 71  |  Size: 8.7 KiB

After

Width: 80  |  Height: 80  |  Size: 4.2 KiB

16
static/clientside/custom.css

@ -5,10 +5,6 @@ img.navbar-brand {
margin: 0 15px 0 0; margin: 0 15px 0 0;
} }
body > .container {
padding: 60px 15px 0;
}
.table thead { .table thead {
background-color: #ddd; background-color: #ddd;
} }
@ -24,15 +20,3 @@ body > .container {
.modal-header.success {background-color: #6ad245;} .modal-header.success {background-color: #6ad245;}
.modal-header.primary {background-color: #789cbb;} .modal-header.primary {background-color: #789cbb;}
html{height: 100%;}
body{
padding: 88px 15px 0;
background-image: url(../img/Transparent_snowman_PNG.png);
background-repeat: no-repeat;
background-position: bottom left;
background-attachment: fixed;
}

BIN
static/img/bcgr.png

Before

Width: 57  |  Height: 50  |  Size: 6.0 KiB

After

Width: 50  |  Height: 49  |  Size: 2.0 KiB

15
static/js/my.js

@ -214,7 +214,7 @@ $(document).ajaxError(function (ev, jqXHR, ajaxSettings, thrownError) {
} }
if(settings.news_url){ if(settings.news_url){
// прверяем новости раз в минуту
// once per minute check news
var tiid = setInterval(check_news, settings.check_interval*1000); var tiid = setInterval(check_news, settings.check_interval*1000);
//Notification.requestPermission(on_ask_perm); //Notification.requestPermission(on_ask_perm);
@ -266,13 +266,20 @@ $(document).ready(function () {
}); });
$('.btn-modal').on('click', function(){ $('.btn-modal').on('click', function(){
$.get(this.href, function(r){
show_ModalMyContent(r);
$.get(this.href, function(response){
console.log(response);
try{
var r = $.parseJSON(response);
console.log(r.text);
window.location.replace(r.url);
}catch (e){
show_ModalMyContent(response);
}
}); });
return false; return false;
}); });
// кнопка посылающая комманду и возвращающая результат выполнения
// button that send command and return response of that
$('.btn-cmd').on('click', function(){ $('.btn-cmd').on('click', function(){
var cmd_param = $(this).attr('data-param'); var cmd_param = $(this).attr('data-param');
var self = $(this); var self = $(this);

2
systemd_units/djing.service

@ -3,7 +3,7 @@ Description=A job for djing
[Service] [Service]
Type=simple Type=simple
ExecStart=/usr/bin/python3 cron.py > /dev/null
ExecStart=/usr/bin/python3 cron.py
WorkingDirectory=/srv/http/djing WorkingDirectory=/srv/http/djing
User=http User=http
Group=http Group=http

2
systemd_units/djing_queue.service

@ -6,7 +6,7 @@ Type=simple
ExecStart=/usr/bin/python3 ./queue_mngr.py > /dev/null ExecStart=/usr/bin/python3 ./queue_mngr.py > /dev/null
PIDFile=/run/queue_mngr.py.pid PIDFile=/run/queue_mngr.py.pid
WorkingDirectory=/var/www/djing WorkingDirectory=/var/www/djing
TimeoutSec=15
TimeoutSec=30
Restart=always Restart=always
User=http User=http
Group=http Group=http

2
systemd_units/djing_rotate.service

@ -4,7 +4,7 @@ Description=A job for rotate djing netflow data
[Service] [Service]
Environment="PATH=/usr/local/sbin:/usr/local/bin:/usr/bin" Environment="PATH=/usr/local/sbin:/usr/local/bin:/usr/bin"
Type=oneshot Type=oneshot
ExecStart=/bin/bash -c "kill -HUP `cat /run/flow.pid.6343`"
ExecStart=/bin/bash -c "kill -HUP `/bin/cat /run/flow.pid.6343`"
User=root User=root
Group=root Group=root

2
systemd_units/djing_telebot.service

@ -3,7 +3,7 @@ Description=Djing telegram bot
[Service] [Service]
Type=simple Type=simple
ExecStart=/usr/bin/python3 ./telebot.py > /dev/null
ExecStart=/usr/bin/python3 ./telebot.py
PIDFile=/run/djing_telebot.pid PIDFile=/run/djing_telebot.pid
WorkingDirectory=/var/www/djing WorkingDirectory=/var/www/djing
TimeoutSec=9 TimeoutSec=9

21
systemd_units/webdav_backup.py

@ -1,7 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import webdav.client as wc
from webdav.client import WebDavException
from sys import argv from sys import argv
from datetime import datetime, timedelta
import webdav.client as wc
options = { options = {
@ -10,11 +10,26 @@ options = {
'webdav_password': "YANDEX PASSWORD" 'webdav_password': "YANDEX PASSWORD"
} }
def remove_old_files(border_time : datetime, client):
# files that older than border_time will be removed
for file in client.list('ISBackups'):
fdate = datetime.strptime(file, 'djing%Y-%m-%d_%H.%M.%S.sql.gz')
if fdate < border_time:
del_fname = 'ISBackups/%' % file
client.clean( del_fname )
print("rm %s" % del_fname)
if __name__ == '__main__': if __name__ == '__main__':
reqfile = argv[1] reqfile = argv[1]
try: try:
client = wc.Client(options) client = wc.Client(options)
if reqfile == 'rotate':
border_time = datetime.now() - timedelta(month=3)
remove_old_files(border_time, client)
else:
client.upload_sync(remote_path="ISBackups/%s" % reqfile, local_path="/var/backups/%s" % reqfile) client.upload_sync(remote_path="ISBackups/%s" % reqfile, local_path="/var/backups/%s" % reqfile)
except WebDavException as we:
except wc.WebDavException as we:
print(we, type(we)) print(we, type(we))

5
taskapp/context_proc.py

@ -1,12 +1,13 @@
from django.contrib.auth.models import AnonymousUser from django.contrib.auth.models import AnonymousUser
from taskapp.models import Task from taskapp.models import Task
from accounts_app.models import UserProfile
def get_active_tasks_count(request): def get_active_tasks_count(request):
tasks_count = 0 tasks_count = 0
if not isinstance(request.user, AnonymousUser):
tasks_count = Task.objects.filter(recipients=request.user, state='S').count()
if isinstance(request.user, UserProfile):
tasks_count = Task.objects.filter(recipients__in=[request.user], state='S').count()
return { return {
'tasks_count': tasks_count 'tasks_count': tasks_count
} }

2
taskapp/handle.py

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
from chatbot.telebot import send_notify from chatbot.telebot import send_notify
from chatbot.models import ChatException from chatbot.models import ChatException
from mydefs import MultipleException from mydefs import MultipleException

6
taskapp/locale/ru/LC_MESSAGES/django.po

@ -34,7 +34,7 @@ msgstr "с. %s\n"
msgid "" msgid ""
"address %(street)s %(house)s.\n" "address %(street)s %(house)s.\n"
"telephone %(telephone)s\n" "telephone %(telephone)s\n"
msgstr "по адресу %s %s тел. %s.\n"
msgstr "по адресу %(street)s %(house)s тел. %(telephone)s.\n"
#: handle.py:31 models.py:25 #: handle.py:31 models.py:25
msgid "not chosen" msgid "not chosen"
@ -456,3 +456,7 @@ msgstr "Исполнители"
msgid "Author does not specified" msgid "Author does not specified"
msgstr "Автор не указан" msgstr "Автор не указан"
msgid "Name and comment count"
msgstr "Имя и количество комментов"

3
taskapp/templates/taskapp/add_edit_task.html

@ -1,6 +1,7 @@
{% extends request.is_ajax|yesno:'bajax.html,base.html' %} {% extends request.is_ajax|yesno:'bajax.html,base.html' %}
{% load i18n %} {% load i18n %}
{% load bootstrap3 %} {% load bootstrap3 %}
{% load globaltags %}
{% block main %} {% block main %}
<ol class="breadcrumb"> <ol class="breadcrumb">
@ -28,7 +29,7 @@
{% else %} {% else %}
<form role="form" action="{% url 'taskapp:add' %}" method="post" enctype="multipart/form-data"> <form role="form" action="{% url 'taskapp:add' %}" method="post" enctype="multipart/form-data">
{% endif %} {% endif %}
{% csrf_token %}<input type="hidden" name="MAX_FILE_SIZE" value="{{ FILE_UPLOAD_MAX_MEMORY_SIZE }}"/>
{% csrf_token %}<input type="hidden" name="MAX_FILE_SIZE" value="{% global_var 'FILE_UPLOAD_MAX_MEMORY_SIZE' %}"/>
{% bootstrap_icon 'tag' as ic %} {% bootstrap_icon 'tag' as ic %}
{% bootstrap_field form.descr addon_before=ic %} {% bootstrap_field form.descr addon_before=ic %}

15
taskapp/templates/taskapp/tasklist.html

@ -7,13 +7,13 @@
<table class="table table-striped table-bordered"> <table class="table table-striped table-bordered">
<thead> <thead>
<tr> <tr>
<th class="col-sm-3">{% trans 'Name' %}</th>
<th class="col-sm-1">{% trans 'Address' %}</th>
<th class="col-sm-1">{% trans 'The nature of the damage' %}</th>
<th class="col-sm-4">{% trans 'Description' %}</th>
<th class="col-sm-1">{% trans 'Task author' %}</th>
<th class="col-sm-1">{% trans 'Date of create' %}</th>
<th class="col-sm-1">{% trans 'Actions' %}</th>
<th class="col-sm-3 col-xs-3">{% trans 'Name and comment count' %}</th>
<th class="col-sm-1 col-xs-2">{% trans 'Address' %}</th>
<th class="col-sm-1 col-xs-1">{% trans 'The nature of the damage' %}</th>
<th class="col-sm-4 col-xs-4">{% trans 'Description' %}</th>
<th class="col-sm-1 col-xs-1">{% trans 'Task author' %}</th>
<th class="col-sm-1 hidden-xs">{% trans 'Date of create' %}</th>
<th class="col-sm-1 col-xs-1">{% trans 'Actions' %}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -40,6 +40,7 @@
<a href="{% url 'abonapp:abon_home' task.abon.group.pk task.abon.pk %}" title="{{ task.abon.description|default:'' }}" data-toggle="tooltip"> <a href="{% url 'abonapp:abon_home' task.abon.group.pk task.abon.pk %}" title="{{ task.abon.description|default:'' }}" data-toggle="tooltip">
{{ task.abon.get_full_name }} {{ task.abon.get_full_name }}
</a> </a>
{% if task.comment_count > 0 %}({{ task.comment_count }}){% endif %}
</td> </td>
<td> <td>
{{ task.abon.group.title }}, {{ task.abon.street|default:'' }} {{ task.abon.house|default:'' }} {{ task.abon.group.title }}, {{ task.abon.street|default:'' }} {{ task.abon.house|default:'' }}

3
taskapp/templates/taskapp/tasklist_all.html

@ -19,7 +19,7 @@
<table class="table table-striped table-bordered"> <table class="table table-striped table-bordered">
<thead> <thead>
<tr> <tr>
<th class="col-sm-2">{% trans 'Name' %}</th>
<th class="col-sm-2">{% trans 'Name and comment count' %}</th>
<th class="col-sm-2">{% trans 'Address' %}</th> <th class="col-sm-2">{% trans 'Address' %}</th>
<th class="col-sm-1">{% trans 'The nature of the damage' %}</th> <th class="col-sm-1">{% trans 'The nature of the damage' %}</th>
<th class="col-sm-3">{% trans 'Description' %}</th> <th class="col-sm-3">{% trans 'Description' %}</th>
@ -53,6 +53,7 @@
<a href="{% url 'abonapp:abon_home' task.abon.group.pk task.abon.pk %}"> <a href="{% url 'abonapp:abon_home' task.abon.group.pk task.abon.pk %}">
{{ task.abon.get_full_name }} {{ task.abon.get_full_name }}
</a> </a>
{% if task.comment_count > 0 %}({{ task.comment_count }}){% endif %}
</td> </td>
<td>{{ task.abon.group.title }}, {{ task.abon.street|default:_('Not assigned') }} {{ task.abon.house|default:_('Not assigned') }}</td> <td>{{ task.abon.group.title }}, {{ task.abon.street|default:_('Not assigned') }} {{ task.abon.house|default:_('Not assigned') }}</td>

10
taskapp/templates/taskapp/tasklist_failed.html

@ -46,8 +46,14 @@
<td>{{ task.get_mode_display }}</td> <td>{{ task.get_mode_display }}</td>
<td>{{ task.descr }}</td> <td>{{ task.descr }}</td>
<td><a href="{% url 'acc_app:other_profile' task.author.pk %}" data-toggle="tooltip"
title="{{ task.author.get_full_name }}">{{ task.author.username }}</a></td>
<td>
{% if task.author %}
<a href="{% url 'acc_app:other_profile' task.author.pk %}" data-toggle="tooltip"
title="{{ task.author.get_full_name }}">{{ task.author.username }}</a>
{% else %}
{% trans 'Not assigned' %}
{% endif %}
</td>
<td class="hidden-xs">{{ task.time_of_create|date:'d E H:i' }}</td> <td class="hidden-xs">{{ task.time_of_create|date:'d E H:i' }}</td>
<td class="btn-group btn-group-sm btn-group-justified"> <td class="btn-group btn-group-sm btn-group-justified">

2
taskapp/templates/taskapp/tasklist_own.html

@ -52,7 +52,7 @@
{% endif %} {% endif %}
<td>{{ task.get_mode_display }}</td> <td>{{ task.get_mode_display }}</td>
<td>{{ task.descr }}</td>
<td>{{ task.descr|default:'' }}</td>
<td>{{ task.get_state_display }}</td> <td>{{ task.get_state_display }}</td>
<td class="hidden-xs">{{ task.time_of_create|date:'d E H:i' }}</td> <td class="hidden-xs">{{ task.time_of_create|date:'d E H:i' }}</td>

5
taskapp/views.py

@ -3,6 +3,7 @@ from json import dumps
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.http import HttpResponse from django.http import HttpResponse
from django.db.models import Count
from django.shortcuts import redirect, get_object_or_404, resolve_url from django.shortcuts import redirect, get_object_or_404, resolve_url
from django.contrib import messages from django.contrib import messages
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
@ -37,6 +38,7 @@ class NewTasksView(BaseTaskListView):
def get_queryset(self): def get_queryset(self):
return Task.objects.filter(recipients=self.request.user, state='S') \ return Task.objects.filter(recipients=self.request.user, state='S') \
.annotate(comment_count=Count('extracomment')) \
.select_related('abon', 'abon__street', 'abon__group', 'author') .select_related('abon', 'abon__street', 'abon__group', 'author')
@ -85,7 +87,8 @@ class AllTasksListView(BaseTaskListView):
context_object_name = 'tasks' context_object_name = 'tasks'
def get_queryset(self): def get_queryset(self):
return Task.objects.select_related('abon', 'abon__street', 'abon__group', 'author')
return Task.objects.annotate(comment_count=Count('extracomment')) \
.select_related('abon', 'abon__street', 'abon__group', 'author')
@login_required @login_required

6
templates/all_base.html

@ -1,7 +1,7 @@
<!DOCTYPE html>
<!DOCTYPE html>{% load globaltags %}
<html lang="ru_RU"> <html lang="ru_RU">
<head> <head>
<title>InternetService - Админка</title>
<title>{% global_var 'COMPANY_NAME' %} - Админка</title>
<meta charset="UTF-8"> <meta charset="UTF-8">
<!--[if lt IE 9]><meta http-equiv="refresh" content="0;URL=/static/bad_ie.html" /><![endif]--> <!--[if lt IE 9]><meta http-equiv="refresh" content="0;URL=/static/bad_ie.html" /><![endif]-->
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
@ -37,7 +37,7 @@
<span class="icon-bar"></span> <span class="icon-bar"></span>
<span class="icon-bar"></span> <span class="icon-bar"></span>
</button> </button>
<a class="navbar-brand hidden-xs" href="{% url 'acc_app:other_profile' request.user.id %}">InternetService</a>
<a class="navbar-brand hidden-xs" href="{% url 'acc_app:other_profile' request.user.id %}">{% global_var 'COMPANY_NAME' %}</a>
</div> </div>
<div class="navbar-collapse collapse"> <div class="navbar-collapse collapse">
<ul class="nav navbar-nav"> <ul class="nav navbar-nav">

Loading…
Cancel
Save