Browse Source

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

devel
Dmitry Novikov 9 years ago
parent
commit
c6df6e3352
  1. 17
      README.md
  2. 89
      abonapp/locale/ru/LC_MESSAGES/django.po
  3. 41
      abonapp/migrations/0022_auto_20170816_1109.py
  4. 232
      abonapp/models.py
  5. 40
      abonapp/templates/abonapp/activate_service.html
  6. 72
      abonapp/templates/abonapp/buy_tariff.html
  7. 12
      abonapp/templates/abonapp/peoples.html
  8. 119
      abonapp/templates/abonapp/service.html
  9. 101
      abonapp/templates/abonapp/services.html
  10. 225
      abonapp/tests.py
  11. 4
      abonapp/urls_abon.py
  12. 83
      abonapp/views.py
  13. 21
      accounts_app/migrations/0007_auto_20170816_1109.py
  14. 5
      bugs.txt
  15. 81
      clientsideapp/locale/ru/LC_MESSAGES/django.po
  16. 3
      clientsideapp/templates/clientsideapp/index.html
  17. 28
      clientsideapp/templates/clientsideapp/modal_activate_service.html
  18. 32
      clientsideapp/templates/clientsideapp/modal_complete_service.html
  19. 10
      clientsideapp/templates/clientsideapp/modal_service_buy.html
  20. 26
      clientsideapp/templates/clientsideapp/modal_unsubscribe_service.html
  21. 134
      clientsideapp/templates/clientsideapp/services.html
  22. 3
      clientsideapp/urls.py
  23. 109
      clientsideapp/views.py
  24. 45
      cron.py
  25. 20
      devapp/migrations/0007_auto_20170816_1109.py
  26. 1
      devapp/models.py
  27. 0
      djing/utils/save_from_nodeny.py
  28. 80
      docs/install.md
  29. 7
      docs/intro.md
  30. 4
      tariff_app/templates/tariff_app/tarifs.html
  31. 73
      taskapp/handle.py
  32. 2
      taskapp/handle.sh
  33. 25
      taskapp/migrations/0015_auto_20170816_1109.py
  34. 18
      taskapp/models.py
  35. 8
      taskapp/views.py

17
README.md

@ -2,19 +2,6 @@
Попытка интернет биллинга. djing сокращение от django-billing. Это web интерфейс управления абонентами интернет-провайдера.
Сейчас идёт тестирвоание работы на Mikrotik, функционал пока минимальный, т.к. пишу в свободное время. Работа планируется в реальных условиях и на реальных абонентах.
Использовано python 3, django 1.9, bootstrap 3, и другое в файле requirements.txt
Может:
Наблюдать за устройствами по snmp
Отправлять изменения мгновенно на mikrotik
Привязывать на карте к точкам топологии устройства
Есть привязка монтажника к группам абонентов
Есть менеджер задач на абонентов. Это оператор может выбрать абонента и описать проблему. Система отправит оповещение через telegram ответственному за групу указанного абонента монтажнику с текстом проблемы, адресом и телефоном абонента.
## Установка(не завершил описание):
На ArchLinux нужые пакеты я устанавливаю так:
```
# pacman -Sy mariadb-clients python3 python-pip nginx uwsgi
```
Дальше ставим всё для python через pip:
```
# pip install git+https://github.com/nerosketch/djing.git
```
## Содержание
* [Установка](./docs/install.md)

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

@ -20,10 +20,8 @@ msgstr ""
"%100>=11 && n%100<=14)? 2 : 3);\n"
#: abonapp/formfields.py:12
#, fuzzy
#| msgid "Enter a valid MAC Address."
msgid "Enter a valid integer."
msgstr "Введите валидный mac адрес"
msgstr "Введите верное число"
#: abonapp/formfields.py:21
msgid "Enter a valid MAC Address."
@ -57,10 +55,6 @@ msgstr "Не хватает денег на счету"
msgid "finish service perm"
msgstr "Снятие со счёта средств"
#: abonapp/models.py:145
msgid "activate service perm"
msgstr "Активация услуги абонента"
#: abonapp/models.py:162
msgid "Digital field"
msgstr "Цифровое поле"
@ -102,7 +96,6 @@ msgstr "Услуга просрочена, отключаем, и подключ
msgid "Ip address already exist"
msgstr "Такой ip уже у кого-то есть"
#: abonapp/templates/abonapp/activate_service.html:8
#: abonapp/templates/abonapp/addAbon.html:7
#: abonapp/templates/abonapp/addGroup.html:7
#: abonapp/templates/abonapp/addInvoice.html:7
@ -120,30 +113,10 @@ msgstr "Такой ip уже у кого-то есть"
msgid "User groups"
msgstr "Группы абонентов"
#: abonapp/templates/abonapp/activate_service.html:11
#: abonapp/templates/abonapp/activate_service.html:18
#: abonapp/templates/abonapp/services.html:44
msgid "Activate service"
msgstr "Активировать услугу"
#: abonapp/templates/abonapp/activate_service.html:26
#, python-format
msgid ""
"\n"
"Are you sure that you want activate service for the user?<br>\n"
"Note that the account will be removed from his money and open access to the "
"resources of the paid services.<br>\n"
"Maintenance cost is %(amount)s. On account of %(ballance)s, will be "
"%(diff)s<br>\n"
"It will work until %(deadline)s"
msgstr ""
"\n"
"Вы уверены что хотите активировать абоненту эту услугу?<br>Обратите внимание "
"что с его счёта <b>снимутся деньги</b> и откроется доступ к ресурсам "
"оплаченной услуги.<br>Стоимость услуги: %(amount)sруб. На счету %(ballance)s "
"руб, останется %(diff)s руб.<br>Услуга будет действовать до %(deadline)s"
#: abonapp/templates/abonapp/activate_service.html:34
#: abonapp/templates/abonapp/addAbon.html:91
#: abonapp/templates/abonapp/addGroup.html:29
#: abonapp/templates/abonapp/addInvoice.html:46
@ -457,20 +430,20 @@ msgstr "Сбросить option82"
msgid "Delete"
msgstr "Удалить"
#: abonapp/templates/abonapp/editAbon.html:205
#: abonapp/templates/abonapp/editAbon.html:178
msgid "Extra fields"
msgstr "Динамические записи"
#: abonapp/templates/abonapp/editAbon.html:228 abonapp/views.py:835
#: abonapp/templates/abonapp/editAbon.html:201 abonapp/views.py:824
msgid "Extra field does not exist"
msgstr "Поле не найдено"
#: abonapp/templates/abonapp/editAbon.html:233
#: abonapp/templates/abonapp/editAbon.html:206
#: abonapp/templates/abonapp/modal_extra_field.html:6
msgid "Add extra field"
msgstr "Добавить новое динамическое поле"
#: abonapp/templates/abonapp/editAbon.html:234
#: abonapp/templates/abonapp/editAbon.html:207
#: abonapp/templates/abonapp/modal_extra_field.html:37
msgid "Add"
msgstr "Добавить"
@ -641,17 +614,14 @@ msgstr "Обновить абонентов в NAS"
msgid "Tariffs in groups"
msgstr "Тарифы в группах"
#: abonapp/templates/abonapp/peoples.html:143
msgid "Streets"
msgstr "Улицы"
#: abonapp/templates/abonapp/peoples.html:148
msgid "No streets found for that group"
msgstr "Не найдены улицы для группы"
#: abonapp/templates/abonapp/services.html:5
msgid "Services of subscriber"
msgstr "Купленные абонентом услуги (назначенные тарифные планы)"
msgid "Subscriber's service"
msgstr "Текущая услуга абонента"
msgid "Add street"
msgstr "Добавить улицу"
@ -696,18 +666,6 @@ msgstr "Исходящая скорость"
msgid "Works until"
msgstr "Действует до"
#: abonapp/templates/abonapp/services.html:15
msgid "Do"
msgstr "Действия"
#: abonapp/templates/abonapp/services.html:52
msgid "Priority up"
msgstr "Повысить приоритет"
#: abonapp/templates/abonapp/services.html:58
msgid "Priority down"
msgstr "Понизить приоритет"
#: abonapp/templates/abonapp/services.html:64
msgid "Delete service"
msgstr "Удалить услугу"
@ -927,13 +885,15 @@ msgstr "История задач"
#: abonapp/templates/abonapp/ext.html:53
msgid "Charts"
msgstr "График"
msgstr "Графики"
#: abonapp/templates/abonapp/ext.html:26
msgid "Sub information"
msgstr "Информация абонента"
#: abonapp/templates/abonapp/ext.html:58
msgid "Streets"
msgstr "Улицы"
msgid "Dialing"
msgstr "Звонки"
@ -961,3 +921,30 @@ msgstr "Метод не POST"
msgid "Call to"
msgstr "Позвонить"
msgid "That service already activated"
msgstr "Эта услуга уже подключена"
msgid "Service already activated"
msgstr "Услуга уже подключена"
msgid "We have a problem in DB: AbonTariff instance has no related to service"
msgstr "У нас проблема с БД: экземпляр AbonTariff не имеет отношения к тарифу"
msgid "Date of start"
msgstr "Дата начала"
msgid "Subscriber has no service"
msgstr "У абонента нет услуги"
msgid "This group has no services"
msgstr "У этой группы нет услуг"
msgid "Attach serices to groups"
msgstr "Привязать услуги к группам"
msgid "Attach services to group"
msgstr "Привязать услуги к этой группе"
msgid "User that is no staff can not buy admin services"
msgstr "Пользователь, который не является персоналом не может покупать услуги для внутренних нужд"

41
abonapp/migrations/0022_auto_20170816_1109.py

@ -0,0 +1,41 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2017-08-16 11:09
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('abonapp', '0021_auto_20170705_1403'),
]
operations = [
migrations.AlterModelOptions(
name='abontariff',
options={'permissions': (('can_complete_service', 'Снятие со счёта средств'),)},
),
migrations.RemoveField(
model_name='abon',
name='current_tariffs',
),
migrations.AddField(
model_name='abon',
name='current_tariff',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='abonapp.AbonTariff'),
),
migrations.AlterUniqueTogether(
name='abontariff',
unique_together=set([]),
),
migrations.RemoveField(
model_name='abontariff',
name='abon',
),
migrations.RemoveField(
model_name='abontariff',
name='tariff_priority',
),
]

232
abonapp/models.py

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

40
abonapp/templates/abonapp/activate_service.html

@ -1,40 +0,0 @@
{% extends request.is_ajax|yesno:'bajax.html,base.html' %}
{% load i18n %}
{% block main %}
<ol class="breadcrumb">
<li><span class="glyphicon glyphicon-home"></span></li>
<li><a href="{% url 'abonapp:group_list' %}">{% trans 'User groups' %}</a></li>
<li><a href="{% url 'abonapp:people_list' abon_group.id %}">{{ abon_group.title }}</a></li>
<li><a href="{% url 'abonapp:abon_home' abon_group.id abon.id %}">{{ abon.fio }}</a></li>
<li class="active">{% trans 'Activate service' %}</li>
</ol>
{% include 'message_block.html' %}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{% trans 'Activate service' %}</h3>
</div>
<div class="panel-body">
<form role="form" action="{% url 'abonapp:activate_service' abon_group.id abon.id abtar.id %}"
method="post">{% csrf_token %}
<input name="finish_confirm" value="yes" type="hidden">
<p>
{% blocktrans with ballance=abon.ballance deadline=deadline|date:'d F Y, H:i:s' %}
Are you sure that you want activate service for the user?<br>
Note that the account will be removed from his money and open access to the resources of the paid services.<br>
Maintenance cost is {{ amount }}. On account of {{ ballance }}, will be {{ diff }}<br>
It will work until {{ deadline }}{% endblocktrans %}
</p>
<button type="submit" class="btn btn-sm btn-primary">
<span class="glyphicon glyphicon-save"></span> {% trans 'Save' %}
</button>
</form>
</div>
</div>
{% endblock %}

72
abonapp/templates/abonapp/buy_tariff.html

@ -22,41 +22,55 @@
<form role="form" action="{% url 'abonapp:pick_tariff' abon_group.pk abon.pk %}"
method="post">{% csrf_token %}
<div class="form-group">
<label for="id_tariff">{% trans 'Pick a service' %}</label>
{% if tariffs %}
<label for="id_tariffs">{% trans 'Pick a service' %}</label>
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-bullhorn"></span></span>
<select class="form-control" name="tariff" id="id_tariffs">
{% for trf in tariffs %}
{% if trf.pk == selected_tariff %}
<option value="{{ trf.pk }}" data-deadline='{{ trf.calc_deadline|date:"Y-m-d" }}' selected>
{% else %}
<option value="{{ trf.pk }}" data-deadline='{{ trf.calc_deadline|date:"Y-m-d" }}'>
{% endif %}
{{ trf.title }}. {{ trf.amount }}{% trans 'currency' %} (Вх:{{ trf.speedIn }}MBit/s. Исх:{{ trf.speedOut }} MBit/s)
</option>
{% endfor %}
</select>
</div>
{% if not abon.active_tariff %}
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-calendar"></span></span>
<input type="text" class="form-control" name="deadline" id="id_deadline" value="{{ tariffs.0.calc_deadline|date:"Y-m-d" }}">
<script type="text/javascript">
$(function () {
$('#id_deadline').datetimepicker({
format: 'YYYY-MM-DD'
});
$('#id_tariffs').on('change', function(){
var a = $(this).find('option:selected');
$('#id_deadline').val(a.attr('data-deadline'));
});
});
</script>
</div>
{% endif %}
{% else %}
<div class="alert alert-danger" role="alert">
<span class="glyphicon glyphicon-exclamation-sign"></span>
{% trans 'This group has no services' %},
<a href="{% url 'abonapp:ch_group_tariff' abon_group.pk %}">
{% trans 'Attach serices to groups' %}
</a>
</div>
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-bullhorn"></span></span>
<select class="form-control" name="tariff" id="id_tariffs">
{% for trf in tariffs %}
<option value="{{ trf.pk }}" data-deadline="{{ trf.calc_deadline|date:"Y-m-d" }}">
{{ trf.title }}. {{ trf.amount }}{% trans 'currency' %} (Вх:{{ trf.speedIn }}MBit/s. Исх:{{ trf.speedOut }} MBit/s)
</option>
{% endfor %}
</select>
</div>
{% if not abon.active_tariff %}
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-calendar"></span></span>
<input type="text" class="form-control" name="deadline" id="id_deadline" value="{{ tariffs.0.calc_deadline|date:"Y-m-d" }}">
<script type="text/javascript">
$(function () {
$('#id_deadline').datetimepicker({
format: 'YYYY-MM-DD'
});
$('#id_tariffs').on('change', function(){
var a = $(this).find('option:selected');
$('#id_deadline').val(a.attr('data-deadline'));
});
});
</script>
</div>
{% endif %}
</div>
<div class="btn-group">
<button type="submit" class="btn btn-sm btn-primary">
<button type="submit" class="btn btn-sm btn-primary"{% if not tariffs %} disabled{% endif %}>
<span class="glyphicon glyphicon-save"></span> {% trans 'Save' %}
</button>
<button type="reset" class="btn btn-sm btn-default">
<button type="reset" class="btn btn-sm btn-default"{% if not tariffs %} disabled{% endif %}>
<span class="glyphicon glyphicon-remove-circle"></span> {% trans 'Reset' %}
</button>
</div>

12
abonapp/templates/abonapp/peoples.html

@ -71,6 +71,8 @@
{% endif %}
<td>{% if human.stat_cache and human.stat_cache.is_online %}
<span class="glyphicon glyphicon-ok text-success"></span>
{% else %}
<span class="glyphicon glyphicon-remove-sign text-muted"></span>
{% endif %}</td>
<td>
<a href="{% url 'abonapp:abon_home' human.group.pk human.pk %}">{{ human.username }}</a>
@ -91,10 +93,10 @@
<td><a href="tel:{{ human.telephone }}">{{ human.telephone }}</a></td>
<td>
{% if human.active_tariff %}
{% if can_ch_trf %}
<a href="{ % url 'tarifs:edit' human.active_tariff.pk %}">{{ human.active_tariff.title }}</a>
{% if perms.tariff_app.change_tariff %}
<a href="{% url 'tarifs:edit' human.active_tariff.tariff.pk %}">{{ human.active_tariff.tariff.title }}</a>
{% else %}
{{ human.active_tariff.title }}
{{ human.active_tariff.tariff.title }}
{% endif %}
{% else %}&mdash;&mdash;&mdash;
{% endif %}
@ -110,7 +112,7 @@
</tr>
{% empty %}
<tr>
<td colspan="12">
<td colspan="11">
{% trans 'Subscribers not found' %}.
{% if perms.abonapp.add_abon %}
<a href="{% url 'abonapp:add_abon' abon_group.pk %}">{% trans 'Add abon' %}</a>
@ -122,7 +124,7 @@
</tbody>
<tfoot>
<tr>
<td colspan="12" class="btn-group">
<td colspan="11" class="btn-group">
{% if perms.abonapp.add_abon %}
<a href="{% url 'abonapp:add_abon' abon_group.pk %}" class="btn btn-sm btn-default" title="Добавить">
<span class="glyphicon glyphicon-plus"></span> {% trans 'Add abon' %}

119
abonapp/templates/abonapp/service.html

@ -0,0 +1,119 @@
{% extends request.is_ajax|yesno:'nullcont.htm,abonapp/ext.htm' %}
{% load i18n %}
{% block content %}
<div class="row">
<div class="col-sm-6">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{% trans "Subscriber's service" %}</h3>
</div>
<div class="panel-body">
{% if abon_tariff %}
<dl class="dl-horizontal">
<dt>{% trans 'Service' %}</dt>
<dd>
{% if abon_tariff.tariff %}
{% if perms.tariff_app.change_tariff %}
<a href="{% url 'tarifs:edit' abon_tariff.tariff.pk %}" title="{{ abon_tariff.time_start|default:'' }}">
{{ abon_tariff.tariff.title }}
</a>
{% else %}
{{ abon_tariff.tariff.title }}
{% endif %}
{% else %}
<b class="text-danger">{% trans 'We have a problem in DB: AbonTariff instance has no related to service' %}</b>
{% endif %}
</dd>
<dt>{% trans 'Sum' %}</dt>
<dd>{{ abon_tariff.tariff.amount }} {% trans 'currency' %}.</dd>
<dt>{% trans 'Input speed' %}</dt>
<dd>{{ abon_tariff.tariff.speedIn }}</dd>
<dt>{% trans 'Output speed' %}</dt>
<dd>{{ abon_tariff.tariff.speedOut }}</dd>
<dt>{% trans 'Date of start' %}</dt>
<dd>{{ abon_tariff.time_start|date:"d E Y, l H:i" }}</dd>
<dt>{% trans 'Works until' %}</dt>
<dd>{{ abon_tariff.deadline|date:"d E Y, l H:i" }}</dd>
</dl>
<blockquote>
<p>{{ abon_tariff.tariff.descr }}</p>
</blockquote>
{% else %}
{% trans 'Subscriber has no service' %}.
<a href="{% url 'abonapp:pick_tariff' abon_group.pk abon.pk %}">
{% trans 'Buy service' %}
</a>
{% endif %}
{% if abon_tariff %}
<a href="{% url 'abonapp:unsubscribe_service' abon_group.pk abon.pk abon_tariff.pk %}" class="btn btn-danger">
<span class="glyphicon glyphicon-remove-circle"></span> {% trans 'Finish service' %}
</a>
{% endif %}
</div>
</div>
</div>
<div class="col-sm-6">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Услуги для заказа</h3>
</div>
<div class="panel-body">
<table class="table table-condensed">
<thead>
<tr>
<th>{% trans 'Pick a service' %}</th>
<th>{% trans 'Service' %}</th>
<th>{% trans 'Price' %}</th>
<th>{% trans 'Speed In' %}</th>
<th>{% trans 'Speed Out' %}</th>
</tr>
</thead>
<tbody>
{% with can_ch_trf=perms.tariff_app.change_tariff %}
{% for service in services %}
<tr>
<td><a href="{% url 'abonapp:pick_tariff' abon_group.pk abon.pk %}?selected_tariff={{ service.pk }}"
class="btn btn-sm btn-default" title="{{ service.get_calc_type_display }}" data-toggle="tooltip"{% if abon_tariff %} disabled{% endif %}>
<span class="glyphicon glyphicon-shopping-cart"></span>
</a></td>
<td>
{% if can_ch_trf %}
<a href="{% url 'tarifs:edit' service.pk %}" title="{{ service.descr }}" data-toggle="tooltip"><b>{{ service.title }}</b></a>
{% else %}
{{ service.title }}
{% endif %}
</td>
<td>{{ service.amount }} {% trans 'currency' %}</td>
<td>{{ service.speedIn }}</td>
<td>{{ service.speedOut }}</td>
</tr>
{% empty %}
<tr><td colspan="5">
{% trans 'This group has no services' %}
<a href="{% url 'abonapp:ch_group_tariff' abon_group.pk %}" class="btn btn-sm btn-default" title="{% trans 'User groups' %}">
<span class="glyphicon glyphicon-cog"></span> {% trans 'Tariffs in groups' %}
</a>
</td></tr>
{% endfor %}
{% endwith %}
</tbody>
</table>
<a href="{% url 'abonapp:ch_group_tariff' abon_group.pk %}" class="btn btn-sm btn-default" title="{% trans 'User groups' %}">
<span class="glyphicon glyphicon-cog"></span> {% trans 'Attach services to group' %}
</a>
</div>
</div>
</div>
</div>
{% endblock %}

101
abonapp/templates/abonapp/services.html

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

225
abonapp/tests.py

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

4
abonapp/urls_abon.py

@ -18,17 +18,15 @@ urlpatterns = [
url(r'^(?P<uid>\d+)/addinvoice$', views.add_invoice, name='add_invoice'),
url(r'^(?P<uid>\d+)/pick$', views.pick_tariff, name='pick_tariff'),
url(r'^(?P<uid>\d+)/chpriority$', views.chpriority, name='chpriority_tariff'),
url(r'^(?P<uid>\d+)/passport_view$', views.passport_view, name='passport_view'),
url(r'^(?P<uid>\d+)/complete_service(?P<srvid>\d+)$', views.complete_service, name='compl_srv'),
url(r'^(?P<uid>\d+)/activate_service(?P<srvid>\d+)$', views.activate_service, name='activate_service'),
url(r'^(?P<uid>\d+)/chart$', views.charts, name='charts'),
url(r'^(?P<uid>\d+)/dials$', views.dials, name='dials'),
url(r'^(?P<uid>\d+)/extra_field$', views.make_extra_field, name='extra_field'),
url(r'^(?P<uid>\d+)/extra_field/(?P<fid>\d+)/delete$', views.extra_field_delete, name='extra_field_delete'),
url(r'^(?P<uid>\d+)/extra_field/edit$', views.extra_field_change, name='extra_field_edit'),
url(r'^(?P<uid>\d+)/unsubscribe_service(?P<srvid>\d+)$', views.unsubscribe_service,
url(r'^(?P<uid>\d+)/unsubscribe_service(?P<abon_tariff_id>\d+)$', views.unsubscribe_service,
name='unsubscribe_service'),
url(r'^(?P<uid>\d+)/dev/$', views.dev, name='dev'),

83
abonapp/views.py

@ -245,16 +245,14 @@ def pay_history(request, gid, uid):
@login_required
@mydefs.only_admins
def abon_services(request, gid, uid):
grp = get_object_or_404(models.AbonGroup, pk=gid)
abon = get_object_or_404(models.Abon, pk=uid)
abon_tarifs = models.AbonTariff.objects.filter(abon=uid)
active_abontariff = abon_tarifs.exclude(time_start=None)
return render(request, 'abonapp/services.html', {
return render(request, 'abonapp/service.html', {
'abon': abon,
'abon_tarifs': abon_tarifs,
'active_abontariff_id': active_abontariff[0].id if active_abontariff.count() > 0 else None,
'abon_group': abon.group
'abon_tariff': abon.current_tariff,
'abon_group': abon.group,
'services': grp.tariffs.all()
})
@ -391,32 +389,11 @@ def pick_tariff(request, gid, uid):
return render(request, 'abonapp/buy_tariff.html', {
'tariffs': tariffs,
'abon': abon,
'abon_group': grp
'abon_group': grp,
'selected_tariff': mydefs.safe_int(request.GET.get('selected_tariff'))
})
@login_required
@mydefs.only_admins
def chpriority(request, gid, uid):
t = request.GET.get('t')
act = request.GET.get('a')
current_abon_tariff = get_object_or_404(models.AbonTariff, pk=t)
try:
if act == 'up':
current_abon_tariff.priority_up()
elif act == 'down':
current_abon_tariff.priority_down()
except (NasFailedResult, NasNetworkError) as e:
messages.error(request, e)
except mydefs.MultipleException as errs:
for err in errs.err_list:
messages.add_message(request, messages.constants.ERROR, err)
return redirect('abonapp:abon_home', gid=gid, uid=uid)
@login_required
@permission_required('abonapp.can_complete_service')
@atomic
@ -477,46 +454,11 @@ def complete_service(request, gid, uid, srvid):
})
@login_required
@permission_required('abonapp.can_activate_service')
@atomic
def activate_service(request, gid, uid, srvid):
abtar = get_object_or_404(models.AbonTariff, pk=srvid)
amount = abtar.calc_amount_service()
try:
if request.method == 'POST':
if request.POST.get('finish_confirm') != 'yes':
return HttpResponse(_('Not confirmed'))
abtar.activate(request.user)
abtar.abon.save()
messages.success(request, _('Service has been activated successfully'))
return redirect('abonapp:abon_services', gid, uid)
except (NasFailedResult, mydefs.LogicError) as e:
messages.error(request, e)
except NasNetworkError as e:
messages.warning(request, e)
except mydefs.MultipleException as errs:
for err in errs.err_list:
messages.add_message(request, messages.constants.ERROR, err)
calc_obj = abtar.tariff.get_calc_type()(abtar)
return render(request, 'abonapp/activate_service.html', {
'abon': abtar.abon,
'abon_group': abtar.abon.group,
'abtar': abtar,
'amount': amount,
'diff': abtar.abon.ballance - amount,
'deadline': calc_obj.calc_deadline()
})
@login_required
@permission_required('abonapp.delete_abontariff')
def unsubscribe_service(request, gid, uid, srvid):
def unsubscribe_service(request, gid, uid, abon_tariff_id):
try:
get_object_or_404(models.AbonTariff, pk=int(srvid)).delete()
get_object_or_404(models.AbonTariff, pk=int(abon_tariff_id)).delete()
messages.success(request, _('User has been detached from service'))
except NasFailedResult as e:
messages.error(request, e)
@ -525,7 +467,7 @@ def unsubscribe_service(request, gid, uid, srvid):
except mydefs.MultipleException as errs:
for err in errs.err_list:
messages.add_message(request, messages.constants.ERROR, err)
return redirect('abonapp:abon_home', gid=gid, uid=uid)
return redirect('abonapp:abon_services', gid=gid, uid=uid)
@login_required
@ -706,7 +648,8 @@ def charts(request, gid, uid):
abontariff = abon.active_tariff()
if abontariff is not None:
high = abontariff.speedIn + abontariff.speedOut
trf = abontariff.tariff
high = trf.speedIn + trf.speedOut
if high > 100:
high = 100
@ -932,7 +875,7 @@ def street_del(request, gid, sid):
def abons(request):
ablist = [{
'id': abn.pk,
'tarif_id': abn.active_tariff().pk if abn.active_tariff() else 0,
'tarif_id': abn.active_tariff().tariff.pk if abn.active_tariff() is not None else 0,
'ip': abn.ip_address.int_ip(),
'is_active': abn.is_active
} for abn in models.Abon.objects.all()]

21
accounts_app/migrations/0007_auto_20170816_1109.py

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2017-08-16 11:09
from __future__ import unicode_literals
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts_app', '0006_auto_20170416_1029'),
]
operations = [
migrations.AlterField(
model_name='userprofile',
name='telephone',
field=models.CharField(max_length=16, validators=[django.core.validators.RegexValidator('^\\+[7,8,9,3]\\d{10,11}$')], verbose_name='Телефон'),
),
]

5
bugs.txt

@ -7,3 +7,8 @@
- Доделать везде переводы
- Не надо коннектиться к микротику когда не собираемся ничего изменять. А то при сохранении залогинились и вышли без действий
- Не удаляет просроченные услуги если не пингуется NAS
- Надо отменить учёт временной зоны
!!! Обязательно проверить как отрабатывает на NAS удаление и изменение AbonTariff
!!! Удалить всё что связано с активацией услуги
!!! Убрать досрочное завершение услуги
! Проверить дату завершения услуги

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

@ -68,81 +68,15 @@ msgstr "Заказать услугу"
msgid "Are you sure you want to order the service?"
msgstr "Вы уверены что хотите заказать услугу?"
#: clientsideapp/templates/clientsideapp/modal_service_buy.html:10
#, python-format
msgid ""
"Your current service <a href=\"#dv\">%(current_service.title)s</a>\n"
"for the <b>%(amount)s</b> rub."
msgstr ""
"Ваша текущая услуга <a href=\"#dv\">%(current_service.title)s</a>\n"
"за <b>%(amount)s</b> руб."
#: clientsideapp/templates/clientsideapp/modal_service_buy.html:13
msgid ""
"You do not have an active service for the use of resources required service "
"purchase from the selection below.<br>\n"
"And if you have already booked then just activate the service from the list "
"ordered."
msgstr ""
"У вас нет активной услуги, для использования ресурсов приобретите нужную "
"услугу из представленных ниже.<br>\n"
"А если уже заказали то просто активируйте услугу из списка заказанных."
#: clientsideapp/templates/clientsideapp/modal_service_buy.html:21
#, python-format
msgid ""
"Inbound speed: %(speedIn)s MBit/s<br>\n"
"Outgoing speed: %(speedOut)s MBit/s<br>\n"
"Cost: %(amount)s rubles."
msgstr ""
"Входящая скорость: %(speedIn)s MBit/s<br>\n"
"Исходящая скорость: %(speedOut)s MBit/s<br>\n"
"Стоимость: %(amount)s руб."
#: clientsideapp/templates/clientsideapp/modal_service_buy.html:28
msgid "Pick"
msgstr "Заказать"
#: clientsideapp/templates/clientsideapp/modal_service_buy.html:30
#: clientsideapp/templates/clientsideapp/modal_unsubscribe_service.html:24
msgid "Close"
msgstr "Закрыть"
#: clientsideapp/templates/clientsideapp/modal_unsubscribe_service.html:5
msgid "Unsubscribe from service"
msgstr "Отписаться от услуги"
#: clientsideapp/templates/clientsideapp/modal_unsubscribe_service.html:8
msgid "Are you sure you want to unsubscribe from the service?"
msgstr "Вы уверены что хотите отписаться от услуги?"
#: clientsideapp/templates/clientsideapp/modal_unsubscribe_service.html:14
#, fuzzy, python-format
#| msgid ""
#| "Inbound speed: %(service.speedIn)s MBit/s<br>\n"
#| "Outgoing speed: %(service.speedOut)s MBit/s<br>\n"
#| "Cost: %(service.amount)s rubles."
msgid ""
"Inbound speed: %(service.speedIn)s MBit/s<br>Outgoing speed: "
"%(service.speedOut)s MBit/s"
msgstr ""
"Входящая скорость: %(service.speedIn)s MBit/s<br>\n"
"Исходящая скорость: %(service.speedOut)s MBit/s"
#: clientsideapp/templates/clientsideapp/modal_unsubscribe_service.html:16
msgid ""
"When you unsubscribe from the service, it just will remove it from the queue "
"inclusions your services.<br>\n"
"Your funds will not be affected, the money will not go away."
msgstr ""
"Когда вы отпишитесь от услуги, это просто уберёт её из очереди включений "
"ваших услуг.<br>\n"
"Ваши средства не буду затронуты, деньги не уйдут."
#: clientsideapp/templates/clientsideapp/modal_unsubscribe_service.html:22
msgid "Unsubscribe"
msgstr "Отписаться"
#: clientsideapp/templates/clientsideapp/pays.html:6
msgid "conducted payments"
msgstr "Проведённые платежи"
@ -168,14 +102,6 @@ msgstr "У вас нет проведённых платежей"
msgid "Buy the service via user side, service '%s'"
msgstr "Покупка тарифного плана через личный кабинет, тариф '%s'"
#: clientsideapp/views.py:55
msgid ""
"You are subscribed on new service. That will enable service when your "
"current service has expired."
msgstr ""
"Вы подписались на новую услугу. Она встала на очередь подключений. Когда "
"закончится ваша текущая услуга, то включится эта"
#: clientsideapp/views.py:82
#, python-format
msgid "Service '%s' has been finished"
@ -195,11 +121,6 @@ msgstr "Действие не подтверждено"
msgid "Temporary network bug"
msgstr "Временные неполадки в сети"
#: clientsideapp/views.py:117
#, python-format
msgid "You has been successfully unsubscribed from service, '%s'"
msgstr "Вы успешно отписались от услуги, '%s'"
#: clientsideapp/views.py:126
msgid "The service was not found"
msgstr "Указанная подписка на услугу не найдена"
@ -207,7 +128,7 @@ msgstr "Указанная подписка на услугу не найден
#: clientsideapp/views.py:145
#, python-format
msgid "The service '%s' wan successfully activated"
msgstr "Услуга '%s' успешно активирована"
msgstr "Услуга '%s' успешно подключена"
#: clientsideapp/views.py:181
msgid "Are you not sure that you want buy the service?"

3
clientsideapp/templates/clientsideapp/index.html

@ -9,8 +9,7 @@
<li><b>Телефоны:</b> +79788328885, +79788318999</li>
<li><b>Адрес:</b> пос. Нижнегорский, ул. Победы 38. 2й этаж старого дома быта, возле рынка</li>
</ul>
<script type="text/javascript" charset="utf-8" async
src="https://api-maps.yandex.ru/services/constructor/1.0/js/?um=constructor%3A6db1b7465ef7d4258318e1ee84048adcb7295b814dbd27a467e9eed21b3c5c8e&amp;width=100%25&amp;height=529&amp;lang=ru_RU&amp;scroll=true"></script>
<script type="text/javascript" charset="utf-8" async src="https://api-maps.yandex.ru/services/constructor/1.0/js/?um=constructor%3A62221c38a606ad870f2cabc9fd476c8ae1a532993915b73553693f7fb5c7de68&amp;width=100%25&amp;height=654&amp;lang=ru_RU&amp;scroll=true"></script>
</div>
</div>

28
clientsideapp/templates/clientsideapp/modal_activate_service.html

@ -1,28 +0,0 @@
<form action="{% url 'client_side:activate_service' abtar.pk %}" method="post">{% csrf_token %}
<div class="modal-header primary">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title"><span class="glyphicon glyphicon-question-sign"></span>Активировать услугу</h4>
</div>
<div class="modal-body">
<h3>Вы уверены что хотите активировать услугу?</h3>
<input type="hidden" name="finish_confirm" value="yes">
<h3>{{ service.title }}</h3>
<p>{{ service.descr }}</p>
<!--<p>Входящая скорость: {{ service.speedIn }} MBit/s<br>
Исходящая скорость: {{ service.speedOut }} MBit/s</p>-->
<p>Вы уверены что хотите активировать эту услугу?<br>
Обратите внимание что с вашего счёта <b>снимутся деньги</b> и откроется доступ к ресурсам оплаченной
услуги.<br>
Стоимость услуги: {{ amount }}руб. На счету {{ abon.ballance }} руб, останется {{ diff }} руб.</p>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-sm btn-success">
<span class="glyphicon glyphicon-shopping-cart"></span> Активировать
</button>
<button type="button" class="btn btn-default" data-dismiss="modal">Закрыть</button>
</div>
</form>

32
clientsideapp/templates/clientsideapp/modal_complete_service.html

@ -1,32 +0,0 @@
<form action="{% url 'client_side:complete_service' abtar.pk %}" method="post">{% csrf_token %}
<div class="modal-header primary">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title"><span class="glyphicon glyphicon-question-sign"></span>Завершить услугу</h4>
</div>
<div class="modal-body">
<h3>Вы уверены что хотите завершить услугу?</h3>
<input type="hidden" name="finish_confirm" value="yes">
<h3>{{ service.title }}</h3>
<p>{{ service.descr }}</p>
<p>Входящая скорость: {{ service.speedIn }} MBit/s<br>
Исходящая скорость: {{ service.speedOut }} MBit/s</p>
<p>Завершение услуги приведёт к прекращению действия услуги. Т.е. интернет отключится.<br>
Деньги за неиспользованные ресурсы возвращены не будут.<br>
Для возобновления обслуживания вы должны купить новую услугу.</p>
Услуга была подключена: {{ abtar.time_start|date:'d F Y, H:i:s' }}<br/>
Сегодня: {% now "d F Y, H:i:s" %}<br/>
Время использования: {{ time_use }}<br/>
Полная стоимость услуги: {{ service.amount }}<br/>
<b>Итоговая стоимость: {{ abtar.calc_amount_service }}</b>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-sm btn-danger">
<span class="glyphicon glyphicon-alert"></span> Завершить
</button>
<button type="button" class="btn btn-default" data-dismiss="modal">Закрыть</button>
</div>
</form>

10
clientsideapp/templates/clientsideapp/modal_service_buy.html

@ -6,13 +6,7 @@
</div>
<div class="modal-body">
<h3>{% trans 'Are you sure you want to order the service?' %}</h3>
{% if current_service %}
<p>{% blocktrans with amount=current_service.amount %}Your current service <a href="#dv">{{ current_service.title }}</a>
for the <b>{{ amount }}</b> rub.{% endblocktrans %}</p>
{% else %}
<p>{% blocktrans %}You do not have an active service for the use of resources required service purchase from the selection below.<br>
And if you have already booked then just activate the service from the list ordered.{% endblocktrans %}</p>
{% endif %}
<p>Будте внимательны, после заказа услуги с вашего счёта <b>снимутся средства</b>, и вы сможете пользоваться купленной услугой.</p>
<h3>{{ service.title }}</h3>
@ -25,7 +19,7 @@ Cost: {{ amount }} rubles.{% endblocktrans %}</p>-->
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-sm btn-primary">
<span class="glyphicon glyphicon-save"></span> {% trans 'Pick' %}
<span class="glyphicon glyphicon-shopping-cart"></span> {% trans 'Pick' %}
</button>
<button type="button" class="btn btn-default" data-dismiss="modal">{% trans 'Close' %}</button>
</div>

26
clientsideapp/templates/clientsideapp/modal_unsubscribe_service.html

@ -1,26 +0,0 @@
{% load i18n %}
<form action="{% url 'client_side:unsubscribe_service' abtar.pk %}" method="post">{% csrf_token %}
<div class="modal-header primary">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title"><span class="glyphicon glyphicon-question-sign"></span>{% trans 'Unsubscribe from service' %}</h4>
</div>
<div class="modal-body">
<h3>{% trans 'Are you sure you want to unsubscribe from the service?' %}</h3>
<input type="hidden" name="finish_confirm" value="yes">
<h3>{{ service.title }}</h3>
<p>{{ service.descr }}</p>
<p>{% blocktrans %}Inbound speed: {{ service.speedIn }} MBit/s<br>Outgoing speed: {{ service.speedOut }} MBit/s{% endblocktrans %}</p>
<p>{% blocktrans %}When you unsubscribe from the service, it just will remove it from the queue inclusions your services.<br>
Your funds will not be affected, the money will not go away.{% endblocktrans %}</p>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-sm btn-primary">
<span class="glyphicon glyphicon-alert"></span> {% trans 'Unsubscribe' %}
</button>
<button type="button" class="btn btn-default" data-dismiss="modal">{% trans 'Close' %}</button>
</div>
</form>

134
clientsideapp/templates/clientsideapp/services.html

@ -1,81 +1,72 @@
{% extends 'clientsideapp/ext.html' %}
{% load i18n %}
{% block client_main %}
<div class="page-header">
<h3>Управление услугами</h3>
{% if current_service %}
<p>Ваша текущая услуга <b>{{ current_service.tariff.title }}</b>
за <b>{{ current_service.tariff.amount }}</b> руб.</p>
{% else %}
<p>У вас нет активной услуги, для использования ресурсов приобретите нужную услугу из представленных
ниже.<br>
А если уже заказали, то просто активируйте услугу из списка заказанных.</p>
{% endif %}
<div class="container">
<div class="row">
<div class="col-lg-4">
<h4>Ваши текущие услуги</h4>
<ul class="list-group">
{% for abon_tariff in own_abon_tariffs %}
<li class="list-group-item">
<div class="btn-group">
{% if abon_tariff == current_service %}
<a href="#{% url 'client_side:complete_service' abon_tariff.pk %}"
class="btn btn-xs btn-danger btn-modal disabled" title="Завершить услугу досрочно">
<span class="glyphicon glyphicon-remove"></span>
</a>
{% else %}
{% if not current_service %}
<a href="{% url 'client_side:activate_service' abon_tariff.pk %}"
class="btn btn-xs btn-success btn-modal" title="Активировать">
<span class="glyphicon glyphicon-fire"></span>
</a>
{% endif %}
<a href="{% url 'client_side:unsubscribe_service' abon_tariff.pk %}"
class="btn btn-xs btn-danger btn-modal" title="Удалить услугу из списка">
<span class="glyphicon glyphicon-remove"></span>
</a>
{% endif %}
</div>
<span class="badge">{{ abon_tariff.tariff.amount }}</span>
{% if abon_tariff.is_started %}
<b>{{ abon_tariff.tariff.title }}</b>
{% else %}
{{ abon_tariff.tariff.title }}
{% endif %}
</li>
{% empty %}
<li class="list-group-item">У вас нет заказанных услуг</li>
{% endfor %}
</ul>
</div>
<div class="col-lg-8">
<div class="col-lg-5">
{% if current_service %}
<div class="panel panel-default">
<div class="panel-heading">
Ваша текущая услуга
</div>
<div class="panel-body">
<h3 class="panel-title">{{ current_service.tariff.title }}</h3><br>
<dl class="dl-horizontal">
<dt>Дата подключения</dt>
<dd>{{ current_service.time_start|date:"d E Y, l" }}</dd>
<div class="container-fluid">
<div class="row">
{% for tarif in tarifs %}
<div class="col-lg-12">
<h3>{{ tarif.title }}</h3>
<dt>Время завершения услуги</dt>
<dd>{{ current_service.deadline|date:"d E Y, l" }}</dd>
<i>{{ tarif.amount }} руб.</i>
<p>{{ tarif.descr }}</p>
<dt>Стоимость</dt>
<dd>{{ current_service.tariff.amount }} {% trans 'currency' %}</dd>
</dl>
<p>{{ current_service.tariff.descr }}</p>
</div>
</div>
{% else %}
<div class="alert alert-warning" role="alert">
<span class="glyphicon glyphicon-exclamation-sign"></span>
<strong>Внимание!</strong> У вас нет услуги, для использования ресурсов приобретите нужную услугу из представленных тут.
</div>
{% endif %}
</div>
<div class="col-lg-7">
<div class="panel panel-default">
<div class="panel-heading">
Доступные для заказа услуги
</div>
<div class="panel-body">
<div class="container-fluid">
<div class="row">
{% for tarif in tarifs %}
<div class="col-lg-12">
<h3>{{ tarif.title }}</h3>
{% if request.user.is_staff %}
<a href="#" class="btn btn-primary disabled">
{% else %}
<a href="{% url 'client_side:buy_service' tarif.id %}" class="btn btn-primary btn-modal">
{% endif %}
<span class="glyphicon glyphicon-shopping-cart"></span> Заказать
</a>
</div>
{% empty %}
<div class="col-lg-4">
<h3 class="panel-title">Нет доступных услуг для заказа</h3>
<i>{{ tarif.amount }} руб.</i>
<p>{{ tarif.descr }}</p>
{% if request.user.is_staff or current_service %}
<a href="#" class="btn btn-primary disabled">
{% else %}
<a href="{% url 'client_side:buy_service' tarif.id %}" class="btn btn-primary btn-modal">
{% endif %}
<span class="glyphicon glyphicon-shopping-cart"></span> {% trans 'Pick' %}
</a>
</div>
{% empty %}
<div class="col-lg-4">
<h3 class="panel-title">Нет доступных услуг для заказа</h3>
</div>
{% endfor %}
</div>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
</div>
@ -89,14 +80,11 @@
Как работает личный кабинет, раздел услуг
</div>
<div class="panel-body">
<p>Перед вами список ваших текущих (подключённых) услуг под надписью "<b>Ваши текущие услуги</b>", и список доступных для подключения услуг справа.
Когда у вас нет текущих услуг, то внизу в рамочке вы видите надпись "<b>У вас нет заказанных услуг</b>". Чтобы заказать новую услугу, вам надо нажать на кнопку "<b>Заказать</b>" справа
в разделе доступных услуг. Когда вы заказываете услугу, то видите диалог в котором вам поясняется какую услугу вы подключаете и сколько денег спишется с вашего счёта.</p>
<p>Когда у вас уже есть подключённая услуга и вы заказываете новую услугу, то она встаёт в очередь подключений, деньги в этот момент не снимаются. По истечению текущей услуги в конце месяца вам автоматически подключится услуга
из очереди. Услуга не может быть подключена если на счету не достаточно средств.</p>
<p>Когда вы хотите досрочно завершить услугу, то можете нажать на красный крестик восле вашей активной услуги, в этот момент вам показывается диалог, в котором указываются подробности операции. Вы должны подтвердить действие
нажатием кнопки <b>Завершить</b>. Внимательно прочитайте предупреждение в диалоге, так как там вы увидите что будет с вашими средствами. Если вы нажимаете красный крестик возле услуги которая ещё не стала активной, то она просто
уберётся из очереди на подключение, и ваши средства никак не затронутся.</p>
<p>Перед вами находится 2 столбца, в левом ваша текущая подключённая услуга, а в правом доступные для заказа услуги.
Когда у вас уже есть подключённая услуга то заказать новую вы не можете пока не завершится текущая.</p>
<p>Когда у вас нет действующей подключённой услуги то кнопка заказа станет активной, и вы сможете заказать для себя услугу.
Обратите внимание что именно в этот момент с вашего счёта снимутся деньги в соответствии со стоимостью услуги.
</p>
</div>
</div>
</div>

3
clientsideapp/urls.py

@ -8,9 +8,6 @@ urlpatterns = [
url(r'^pays$', views.pays, name='pays'),
url(r'^services$', views.services, name='services'),
url(r'^services/(?P<srv_id>\d+)/buy$', views.buy_service, name='buy_service'),
url(r'^services/(?P<srv_id>\d+)/finish$', views.complete_service, name='complete_service'),
url(r'^services/(?P<srv_id>\d+)/unsubscribe$', views.unsubscribe_service, name='unsubscribe_service'),
url(r'^services/(?P<srv_id>\d+)/activate$', views.activate_service, name='activate_service'),
url(r'^debts$', views.debts_list, name='debts'),
url(r'^debts/(?P<d_id>\d+)$', views.debt_buy, name='debt_buy')
]

109
clientsideapp/views.py

@ -3,13 +3,12 @@ from django.contrib.auth.decorators import login_required
from django.contrib.gis.shortcuts import render_to_text
from django.shortcuts import render, get_object_or_404, redirect
from django.contrib import messages
from django.utils import timezone
from django.db.transaction import atomic
from django.utils.translation import ugettext_lazy as _
from abonapp.models import AbonLog, AbonTariff, InvoiceForPayment, Abon
from tariff_app.models import Tariff
from mydefs import pag_mn, RuTimedelta, LogicError
from mydefs import pag_mn, LogicError
from agent import NasFailedResult, NasNetworkError
@ -31,14 +30,10 @@ def pays(request):
def services(request):
abon = Abon.objects.get(pk=request.user.pk)
all_tarifs = abon.group.tariffs.filter(is_admin=False)
own_abon_tariffs = AbonTariff.objects.filter(abon=abon)
current_service = own_abon_tariffs.exclude(time_start=None)
current_service = current_service[0] if current_service.count() > 0 else None
return render(request, 'clientsideapp/services.html', {
'tarifs': all_tarifs,
'own_abon_tariffs': own_abon_tariffs,
'current_service': current_service
'current_service': abon.active_tariff()
})
@ -52,12 +47,11 @@ def buy_service(request, srv_id):
if request.method == 'POST':
abon.pick_tariff(service, request.user, _("Buy the service via user side, service '%s'")
% service)
messages.success(request, _('You are subscribed on new service. '
'That will enable service when your current service has expired.'))
messages.success(request, _("The service '%s' wan successfully activated") % service.title)
else:
return render_to_text('clientsideapp/modal_service_buy.html', {
'service': service,
'current_service': current_service
'current_service': current_service.tariff if current_service is not None else None
}, request=request)
except LogicError as e:
messages.error(request, e)
@ -66,101 +60,6 @@ def buy_service(request, srv_id):
return redirect('client_side:services')
@login_required
@atomic
def complete_service(request, srv_id):
abtar = get_object_or_404(AbonTariff, id=srv_id)
service = abtar.tariff
try:
if request.method == 'POST':
# досрочно завершаем услугу
finish_confirm = request.POST.get('finish_confirm')
if finish_confirm == 'yes':
# удаляем запись о текущей услуге.
abtar.delete()
messages.success(request, _("Service '%s' has been finished") % service.title)
AbonLog.objects.create(
abon=abtar.abon,
amount=0.0,
author=abtar.abon,
comment=_("Early terminated service '%s' via client side") % service.title
)
else:
messages.error(request, _('Act is not confirmed'))
else:
time_use = RuTimedelta(timezone.now() - abtar.time_start)
return render_to_text('clientsideapp/modal_complete_service.html', {
'service': service,
'abtar': abtar,
'time_use': time_use
}, request=request)
except LogicError as e:
messages.error(request, e)
except NasFailedResult as e:
messages.error(request, e)
except NasNetworkError:
messages.error(request, _('Temporary network bug'))
return redirect('client_side:services')
@login_required
@atomic
def unsubscribe_service(request, srv_id):
abtar = get_object_or_404(AbonTariff, id=srv_id)
service = abtar.tariff
try:
if request.method == 'POST':
# досрочно завершаем услугу
if request.POST.get('finish_confirm') == 'yes':
AbonTariff.objects.get(pk=srv_id).delete()
messages.success(request, _("You has been successfully unsubscribed from service, '%s'") % service.title)
else:
messages.error(request, _('Act is not confirmed'))
else:
return render_to_text('clientsideapp/modal_unsubscribe_service.html', {
'abtar': abtar,
'service': service
}, request=request)
except AbonTariff.DoesNotExist:
messages.error(request, _('The service was not found'))
except NasFailedResult as e:
messages.error(request, e)
except NasNetworkError:
messages.error(request, _('Temporary network bug'))
return redirect('client_side:services')
@login_required
@atomic
def activate_service(request, srv_id):
abtar = get_object_or_404(AbonTariff, id=srv_id)
service = abtar.tariff
amount = abtar.calc_amount_service()
try:
if request.method == 'POST':
# активируем услугу
if request.POST.get('finish_confirm') == 'yes':
abtar.activate(request.user)
messages.success(request, _("The service '%s' wan successfully activated") % service.title)
else:
messages.error(request, _('Act is not confirmed'))
return redirect('client_side:services')
except NasFailedResult as e:
messages.error(request, e)
except NasNetworkError as e:
messages.warning(request, e)
except LogicError as e:
messages.error(request, e)
return render_to_text('clientsideapp/modal_activate_service.html', {
'abtar': abtar,
'service': service,
'amount': amount,
'abon': abtar.abon,
'diff': abtar.abon.ballance - amount
}, request=request)
@login_required
def debts_list(request):
debts = InvoiceForPayment.objects.filter(abon=request.user)

45
cron.py

@ -10,17 +10,40 @@ from mydefs import LogicError
def main():
try:
users = Abon.objects.filter(is_active=True, is_admin=False).exclude(ip_address=None)
tm = Transmitter()
tm.sync_nas(users)
except (NasNetworkError, NasFailedResult) as er:
print("Error:", er)
exit(1)
except LogicError as er:
print("Notice:", er)
exit(1)
tm = None
users = Abon.objects.all()
for user in users:
try:
# бдим за услугами абонента: просроченные отключить, заказанные подключить
user.bill_service(user)
# если нет ip то и нет смысла лезть в NAS
if user.ip_address is None:
continue
# а есть-ли у абонента доступ к услуге
if not user.is_access():
continue
# строим структуру агента
ab = user.build_agent_struct()
if ab is None:
# если не построилась структура агента, значит нет ip
# а если нет ip то и синхронизировать абонента без ip нельзя
continue
# обновляем абонента если он статический. Иначе его обновит dhcp
if user.opt82 is None:
if tm is None:
tm = Transmitter()
tm.update_user(ab)
except (NasNetworkError, NasFailedResult) as er:
print("Error:", er)
except LogicError as er:
print("Notice:", er)
if __name__ == "__main__":
try:

20
devapp/migrations/0007_auto_20170816_1109.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2017-08-16 11:09
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('devapp', '0006_auto_20170705_1403'),
]
operations = [
migrations.AlterField(
model_name='device',
name='devtype',
field=models.CharField(choices=[('Dl', 'DLink switch'), ('Pn', 'PON OLT'), ('On', 'PON ONU'), ('Ex', 'Eltex switch')], default='Dl', max_length=2),
),
]

1
devapp/models.py

@ -75,6 +75,7 @@ def dev_post_save_signal(sender, instance, **kwargs):
if instance.devtype != 'On':
return
grp = instance.user_group.pk
code = ''
if grp == 87:
code = 'chk'
elif grp == 85:

0
djing/utils/save_from_nodeny.py

80
docs/install.md

@ -0,0 +1,80 @@
## Установка(не завершил описание):
Работа предполагается на python3.
Я предпочитаю запускать wsgi сервер на связке uWSGI + Nginx, так что ставить будем соответствующие пакеты.
На ArchLinux нужые пакеты можно установить так:
```
# pacman -Sy mariadb-clients python3 python-pip nginx uwsgi
```
Дальше ставим всё для python через pip:
```
# pip install git+https://github.com/nerosketch/djing.git
```
### Настройка WEB Сервера
Условимся что путь к папке с проектом находится по адресу: </var/www/djing>
Конфиг Nginx на моём рабочем сервере выглядит так:
user http;
worker_processes auto;
pid /run/nginx.pid;
events {
worker_connections 1024;
}
http {
sendfile on;
upstream djing { server unix:///run/uwsgi/djing.sock; }
server {
listen 80;
server_name <ваш-домен>.com;
root /var/www/djing;
charset utf-8;
# укажите где лежит ваш раздел с медиа для сайта
location /media {
alias /var/www/djing/media;
}
# местоположение статики
location /static {
alias /var/www/djing/static;
}
# тут надо указать путь куда у вас установился Django + путь к статике админки
# путь к Django тут: /usr/lib/python3.5/site-packages/django
# путь к статике соответственно: contrib/admin/static/admin
location /static/admin {
alias /usr/lib/python3.5/site-packages/django/contrib/admin/static/admin;
}
# на корневом url / реагируем с помощью сокета проекта
# у нас он называется "djing": upstream djing { server ...
location / {
uwsgi_pass djing;
include uwsgi_params;
}
}
}
Это минимальный конфиг Nginx для работы. Проверте файл /run/uwsgi/djing.sock на доступность пользователю http для чтения.
Далее настраиваем uWSGI. Мой конфиг для uWSGI в режиме emperor:
[uwsgi]
uid = http
gid = http
pidfile = /run/uwsgi/uwsgi.pid
emperor = /etc/uwsgi.d
stats = /run/uwsgi/stats.sock
chmod-socket = 660
emperor-tyrant = true
cap = setgid,setuid
У меня конфиг лежит по адресу /etc/uwsgi.ini
### Настраиваем системные утилиты
Если ваша система работает с поддержкой *systemd* то в каталоге *systemd_units* проекта вы найдёте юниты для systemd.
Скопируйте их в каталог юнитов systemd

7
docs/intro.md

@ -0,0 +1,7 @@
Текущие возможности:
1. Может наблюдать за устройствами по snmp
2. Отправлять изменения мгновенно на mikrotik
3. Привязывать на карте к точкам топологии устройства
4. Есть привязка монтажника к группам абонентов
5. Есть менеджер задач на абонентов. Это оператор может выбрать абонента и описать проблему. Система отправит оповещение через telegram ответственному за групу указанного абонента монтажнику с текстом проблемы, адресом и телефоном абонента.
6. Долгие или сложные задачи можно отправлять на очередь исполнения

4
tariff_app/templates/tariff_app/tarifs.html

@ -38,7 +38,7 @@
</a>
{% if order_by == 'amount' %}<span class="glyphicon glyphicon-filter"></span>{% endif %}
</th>
<th width="250">{% trans 'Service title' %}</th>
<th width="250">{% trans 'Script' %}</th>
<th width="50">Do</th>
</tr>
</thead>
@ -55,7 +55,7 @@
</td>
<td>{{ tar.speedIn }}</td>
<td>{{ tar.speedOut }}</td>
<td>{{ tar.amount }} руб</td>
<td>{{ tar.amount }} {% trans 'currency' %}</td>
<td>{{ tar.get_calc_type_display }}</td>
<td>
{% if can_del_trf %}

73
taskapp/handle.py

@ -2,41 +2,50 @@
from django.utils.translation import ugettext as _
from chatbot.telebot import send_notify
from chatbot.models import ChatException
from mydefs import MultipleException
class TaskException(Exception):
pass
def handle(task, author, recipient, abon_group):
try:
dst_account = recipient
text = _('Task')
# Если сигнал самому себе то молчим
if author == recipient:
return
# Если задача 'На выполнении' то молчим
if task.state == 'C':
return
# Если задача завершена
elif task.state == 'F':
text = _('Task completed')
# Меняем цель назначения на автора, т.к. при завершении
# идёт оповещение автору о выполнении
dst_account = author
if task.abon is not None:
fulltext="%s:\n%s\n" % (text, task.abon.get_full_name())
else:
fulltext="%s\n" % text
fulltext += _('locality %s.\n') % abon_group.title
if task.abon:
fulltext += _('address %s %s.\ntelephone %s\n') % (
task.abon.street.name if task.abon.street is not None else '<'+_('not chosen')+'>',
task.abon.house,
task.abon.telephone
)
fulltext += _('Task type - %s.') % task.get_mode_display() + '\n'
fulltext += task.descr if task.descr else ''
send_notify(fulltext, dst_account)
except ChatException as e:
raise TaskException(e)
def handle(task, author, recipients, abon_group):
errors = []
for recipient in recipients:
try:
dst_account = recipient
text = _('Task')
# Если сигнал самому себе то молчим
if author == recipient:
return
# Если задача завершена или провалена
elif task.state == 'F' or task.state == 'C':
text = _('Task completed')
if task.abon is not None:
fulltext = "%s:\n%s\n" % (text, task.abon.get_full_name())
else:
fulltext = "%s\n" % text
fulltext += _('locality %s.\n') % abon_group.title
if task.abon:
fulltext += _('address %s %s.\ntelephone %s\n') % (
task.abon.street.name if task.abon.street is not None else '<'+_('not chosen')+'>',
task.abon.house,
task.abon.telephone
)
fulltext += _('Task type - %s.') % task.get_mode_display() + '\n'
fulltext += task.descr if task.descr else ''
print('task.state:', task.state)
if task.state == 'F' or task.state == 'C':
# Если задача завершена или провалена то отправляем одно оповещение автору
try:
send_notify(fulltext, author)
except ChatException as e:
raise TaskException(e)
else:
send_notify(fulltext, dst_account)
except ChatException as e:
errors.append(e)
if len(errors) > 0:
raise MultipleException(errors)

2
taskapp/handle.sh

@ -44,6 +44,6 @@ fi
FULLTEXT="$text: $ABON_FIO. $ABON_ADDR $ABON_TEL. $ABON_GRP. $FAIL_MODE. $DESCR"
echo "TO $RECIPIENT_TEL: $FULLTEXT" >> /tmp/task_sms.log
echo "TO $RECIPIENT_TEL: $FULLTEXT"
/usr/bin/gammu-smsd-inject EMS $RECIPIENT_TEL -text "$FULLTEXT" -unicode

25
taskapp/migrations/0015_auto_20170816_1109.py

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2017-08-16 11:09
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('taskapp', '0014_auto_20170416_1029'),
]
operations = [
migrations.AlterField(
model_name='changelog',
name='act_type',
field=models.CharField(choices=[('e', 'Изменение задачи'), ('c', 'Создание задачи'), ('d', 'Удаление задачи'), ('f', 'Завершение задачи'), ('b', 'Задача провалена')], max_length=1),
),
migrations.AlterField(
model_name='task',
name='state',
field=models.CharField(choices=[('S', 'Новая'), ('C', 'Провалена'), ('F', 'Выполнена')], default='S', max_length=1),
),
]

18
taskapp/models.py

@ -6,8 +6,7 @@ from django.conf import settings
from django.utils import timezone
from django.utils.translation import ugettext as _
from abonapp.models import Abon
from .handle import handle as task_handle, TaskException
from mydefs import MultipleException
from .handle import handle as task_handle
TASK_PRIORITIES = (
@ -121,17 +120,10 @@ def task_handler(sender, instance, **kwargs):
act_type='e',
who=instance.author
)
errors = []
for recipient in instance.recipients.all():
try:
task_handle(
instance, instance.author,
recipient, group
)
except TaskException as e:
errors.append(e)
if len(errors) > 0:
raise MultipleException(errors)
task_handle(
instance, instance.author,
instance.recipients.all(), group
)
#def task_delete(sender, instance, **kwargs):

8
taskapp/views.py

@ -6,6 +6,8 @@ from django.contrib import messages
from abonapp.models import Abon
from django.utils.translation import ugettext as _
from datetime import date
from .handle import TaskException
from .models import Task
from mydefs import pag_mn, only_admins, safe_int, MultipleException, RuTimedelta
from .forms import TaskFrm
@ -152,6 +154,8 @@ def task_add_edit(request, task_id=0):
except MultipleException as errs:
for err in errs.err_list:
messages.add_message(request, messages.constants.ERROR, err)
except TaskException as e:
messages.error(request, e)
return render(request, 'taskapp/add_edit_task.html', {
'form': frm,
@ -169,6 +173,8 @@ def task_finish(request, task_id):
except MultipleException as errs:
for err in errs.err_list:
messages.add_message(request, messages.constants.ERROR, err)
except TaskException as e:
messages.error(request, e)
return redirect('taskapp:home')
@ -189,4 +195,6 @@ def remind(request, task_id):
except MultipleException as errs:
for err in errs.err_list:
messages.add_message(request, messages.constants.ERROR, err)
except TaskException as e:
messages.error(request, e)
return redirect('taskapp:home')
Loading…
Cancel
Save