Browse Source

fix bug

devel
bashmak 8 years ago
parent
commit
4d6995845e
  1. 5
      abonapp/models.py
  2. 6
      abonapp/views.py
  3. 63
      agent/core.py
  4. 183
      agent/mod_mikrotik.py
  5. 14
      agent/settings.py.example
  6. 5
      agent/structs.py
  7. 41
      docs/dev.md
  8. 2
      mydefs.py
  9. 23
      tariff_app/base_intr.py
  10. 28
      tariff_app/custom_tariffs.py
  11. 2
      taskapp/context_proc.py
  12. 2
      taskapp/forms.py

5
abonapp/models.py

@ -1,4 +1,5 @@
from datetime import datetime from datetime import datetime
from typing import Optional, Union
from django.conf import settings from django.conf import settings
from django.core import validators from django.core import validators
@ -277,7 +278,7 @@ class Abon(BaseAccount):
raise LogicError(_('Ip address already exist')) raise LogicError(_('Ip address already exist'))
super(Abon, self).save(*args, **kwargs) super(Abon, self).save(*args, **kwargs)
def sync_with_nas(self, created: bool):
def sync_with_nas(self, created: bool) -> Optional[Union[Exception, bool]]:
timeout = None timeout = None
if hasattr(self, 'is_dhcp') and self.is_dhcp: if hasattr(self, 'is_dhcp') and self.is_dhcp:
timeout = getattr(settings, 'DHCP_TIMEOUT', 14400) timeout = getattr(settings, 'DHCP_TIMEOUT', 14400)
@ -292,7 +293,7 @@ class Abon(BaseAccount):
tm.update_user(agent_abon, ip_timeout=timeout) tm.update_user(agent_abon, ip_timeout=timeout)
except (NasFailedResult, NasNetworkError, ConnectionResetError) as e: except (NasFailedResult, NasNetworkError, ConnectionResetError) as e:
print('ERROR:', e) print('ERROR:', e)
return True
return e
class PassportInfo(models.Model): class PassportInfo(models.Model):

6
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, Http404
from django.http import HttpResponse, HttpResponseBadRequest
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
@ -273,7 +273,9 @@ def abonhome(request, gid, uid):
if newip: if newip:
abon.ip_address = newip abon.ip_address = newip
abon = frm.save() abon = frm.save()
abon.sync_with_nas(created=False)
res = abon.sync_with_nas(created=False)
if isinstance(res, Exception):
messages.warning(request, res)
messages.success(request, _('edit abon success msg')) messages.success(request, _('edit abon success msg'))
else: else:
messages.warning(request, _('fix form errors')) messages.warning(request, _('fix form errors'))

63
agent/core.py

@ -1,6 +1,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from abc import ABCMeta, abstractmethod from abc import ABCMeta, abstractmethod
from .structs import AbonStruct, TariffStruct
from typing import Iterator, Any, Tuple
from .structs import AbonStruct, TariffStruct, VectorAbon, VectorTariff
# Всплывает если из NAS вернулся не удачный результат # Всплывает если из NAS вернулся не удачный результат
@ -13,90 +15,65 @@ class NasNetworkError(Exception):
pass pass
# Проверяет входной объект на принадлежность типу.
# Можно передать несколько типов в соответствии количеству параметров
# В общем желание организовать строгую типизацию :)
def check_input_type(*types):
def real_check(fn):
def wrapped(self, *args):
for param_type, param in zip(types, args):
if not isinstance(param, param_type):
raise TypeError("%s must be %s, but is %s" % (str(param), str(param_type), type(param)))
return fn(self, *args)
return wrapped
return real_check
# Общается с NAS'ом # Общается с NAS'ом
class BaseTransmitter(metaclass=ABCMeta): class BaseTransmitter(metaclass=ABCMeta):
@abstractmethod @abstractmethod
@check_input_type(set)
def add_user_range(self, user_list):
def add_user_range(self, user_list: VectorAbon):
"""добавляем список абонентов в NAS""" """добавляем список абонентов в NAS"""
@abstractmethod @abstractmethod
@check_input_type(set)
def remove_user_range(self, users):
def remove_user_range(self, users: VectorAbon):
"""удаляем список абонентов""" """удаляем список абонентов"""
@abstractmethod @abstractmethod
@check_input_type(AbonStruct)
def add_user(self, user, *args):
def add_user(self, user: AbonStruct, *args):
"""добавляем абонента""" """добавляем абонента"""
@abstractmethod @abstractmethod
@check_input_type(AbonStruct)
def remove_user(self, user):
def remove_user(self, user: AbonStruct):
"""удаляем абонента""" """удаляем абонента"""
@abstractmethod @abstractmethod
@check_input_type(AbonStruct)
def update_user(self, user, *args):
def update_user(self, user: AbonStruct, *args):
"""чтоб обновить абонента можно изменить всё кроме его uid, по uid абонент будет найден""" """чтоб обновить абонента можно изменить всё кроме его uid, по uid абонент будет найден"""
@abstractmethod @abstractmethod
@check_input_type(TariffStruct)
def add_tariff_range(self, tariff_list):
def add_tariff_range(self, tariff_list: VectorTariff):
""" """
Пока не используется, зарезервировано. Пока не используется, зарезервировано.
Добавляет список тарифов в NAS Добавляет список тарифов в NAS
""" """
@abstractmethod @abstractmethod
@check_input_type(TariffStruct)
def remove_tariff_range(self, tariff_list):
def remove_tariff_range(self, tariff_list: VectorTariff):
""" """
Пока не используется, зарезервировано. Пока не используется, зарезервировано.
Удаляем список тарифов по уникальным идентификаторам Удаляем список тарифов по уникальным идентификаторам
""" """
@abstractmethod @abstractmethod
@check_input_type(TariffStruct)
def add_tariff(self, tariff):
def add_tariff(self, tariff: TariffStruct):
""" """
Пока не используется, зарезервировано. Пока не используется, зарезервировано.
Добавляет тариф Добавляет тариф
""" """
@abstractmethod @abstractmethod
@check_input_type(TariffStruct)
def update_tariff(self, tariff):
def update_tariff(self, tariff: TariffStruct):
""" """
Пока не используется, зарезервировано. Пока не используется, зарезервировано.
Чтоб обновить тариф надо изменить всё кроме его tid, по tid тариф будет найден Чтоб обновить тариф надо изменить всё кроме его tid, по tid тариф будет найден
""" """
@abstractmethod @abstractmethod
@check_input_type(TariffStruct)
def remove_tariff(self, tid):
def remove_tariff(self, tid: int):
""" """
:param tid: id тарифа в среде NAS сервера чтоб удалить по этому номеру :param tid: id тарифа в среде NAS сервера чтоб удалить по этому номеру
Пока не используется, зарезервировано. Пока не используется, зарезервировано.
""" """
@abstractmethod @abstractmethod
@check_input_type(str, int)
def ping(self, host, count=10):
def ping(self, host: str, count=10):
""" """
:param host: ip адрес в текстовом виде, например '192.168.0.1' :param host: ip адрес в текстовом виде, например '192.168.0.1'
:param count: количество пингов :param count: количество пингов
@ -110,19 +87,19 @@ class BaseTransmitter(metaclass=ABCMeta):
:return: список AbonStruct :return: список AbonStruct
""" """
def _diff_users(self, users_from_db):
def _diff_users(self, users_from_db: Iterator[Any]) -> Tuple[set, set]:
""" """
:param users_from_db: QuerySet всех абонентов у которых может быть обслуживание :param users_from_db: QuerySet всех абонентов у которых может быть обслуживание
:return: на выходе получаем абонентов которых надо добавить в nas и которых надо удалить :return: на выходе получаем абонентов которых надо добавить в nas и которых надо удалить
""" """
users_from_db = [ab.build_agent_struct() for ab in users_from_db if ab.is_access()]
users_from_db = set([ab for ab in users_from_db if ab is not None and ab.tariff is not None])
users_struct_list = [ab.build_agent_struct() for ab in users_from_db if ab.is_access()]
users_struct_set = set([ab for ab in users_struct_list if ab is not None and ab.tariff is not None])
users_from_nas = set(self.read_users()) users_from_nas = set(self.read_users())
list_for_del = (users_from_db ^ users_from_nas) - users_from_db
list_for_add = users_from_db - users_from_nas
list_for_del = (users_struct_set ^ users_from_nas) - users_struct_set
list_for_add = users_struct_set - users_from_nas
return list_for_add, list_for_del return list_for_add, list_for_del
def sync_nas(self, users_from_db):
def sync_nas(self, users_from_db: Iterator):
list_for_add, list_for_del = self._diff_users(users_from_db) list_for_add, list_for_del = self._diff_users(users_from_db)
if len(list_for_del) > 0: if len(list_for_del) > 0:
print('FOR DELETE') print('FOR DELETE')

183
agent/mod_mikrotik.py

@ -3,14 +3,14 @@ import socket
import binascii import binascii
from abc import ABCMeta from abc import ABCMeta
from hashlib import md5 from hashlib import md5
from typing import List, Iterable
from .core import BaseTransmitter, NasFailedResult, NasNetworkError from .core import BaseTransmitter, NasFailedResult, NasNetworkError
from mydefs import ping, singleton from mydefs import ping, singleton
from .structs import TariffStruct, AbonStruct, IpStruct
from .structs import TariffStruct, AbonStruct, IpStruct, VectorAbon, VectorTariff
from . import settings as local_settings from . import settings as local_settings
from django.conf import settings from django.conf import settings
import re import re
DEBUG = getattr(settings, 'DEBUG', False) DEBUG = getattr(settings, 'DEBUG', False)
LIST_USERS_ALLOWED = 'DjingUsersAllowed' LIST_USERS_ALLOWED = 'DjingUsersAllowed'
@ -19,14 +19,16 @@ LIST_USERS_BLOCKED = 'DjingUsersBlocked'
@singleton @singleton
class ApiRos: class ApiRos:
"Routeros api"
"""Routeros api"""
sk = None sk = None
is_login = False is_login = False
def __init__(self, ip, port):
def __init__(self, ip: str, port: int):
if self.sk is None: if self.sk is None:
sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sk.connect((ip, port or getattr(local_settings, 'NAS_PORT', 8728)))
if port is None:
port = local_settings.NAS_PORT
sk.connect((ip, port or 8728))
self.sk = sk self.sk = sk
self.currenttag = 0 self.currenttag = 0
@ -41,136 +43,145 @@ class ApiRos:
md.update(b'\x00') md.update(b'\x00')
md.update(bytes(pwd, 'utf-8')) md.update(bytes(pwd, 'utf-8'))
md.update(chal) md.update(chal)
for r in self.talk_iter(["/login", "=name=" + username,
"=response=00" + binascii.hexlify(md.digest()).decode('utf-8')]): pass
for _ in self.talk_iter(["/login", "=name=" + username,
"=response=00" + binascii.hexlify(md.digest()).decode('utf-8')]):
pass
self.is_login = True self.is_login = True
def talk_iter(self, words): def talk_iter(self, words):
if self.writeSentence(words) == 0: return
if self.write_sentence(words) == 0:
return
while 1: while 1:
i = self.readSentence()
if len(i) == 0: continue
i = self.read_sentence()
if len(i) == 0:
continue
reply = i[0] reply = i[0]
attrs = {} attrs = {}
for w in i[1:]: for w in i[1:]:
j = w.find('=', 1) j = w.find('=', 1)
if (j == -1):
if j == -1:
attrs[w] = '' attrs[w] = ''
else: else:
attrs[w[:j]] = w[j + 1:] attrs[w[:j]] = w[j + 1:]
yield (reply, attrs) yield (reply, attrs)
if reply == '!done': return
if reply == '!done':
return
def writeSentence(self, words):
def write_sentence(self, words):
ret = 0 ret = 0
for w in words: for w in words:
self.writeWord(w)
self.write_word(w)
ret += 1 ret += 1
self.writeWord('')
self.write_word('')
return ret return ret
def readSentence(self):
def read_sentence(self):
r = [] r = []
while 1: while 1:
w = self.readWord()
if w == '': return r
w = self.read_word()
if w == '':
return r
r.append(w) r.append(w)
def writeWord(self, w):
def write_word(self, w):
if DEBUG: if DEBUG:
print("<<< " + w) print("<<< " + w)
b = bytes(w, "utf-8") b = bytes(w, "utf-8")
self.writeLen(len(b))
self.writeBytes(b)
self.write_len(len(b))
self.write_bytes(b)
def readWord(self):
ret = self.readBytes(self.readLen()).decode('utf-8')
def read_word(self):
ret = self.read_bytes(self.read_len()).decode('utf-8')
if DEBUG: if DEBUG:
print(">>> " + ret) print(">>> " + ret)
return ret return ret
def writeLen(self, l):
def write_len(self, l):
if l < 0x80: if l < 0x80:
self.writeBytes(bytes([l]))
self.write_bytes(bytes([l]))
elif l < 0x4000: elif l < 0x4000:
l |= 0x8000 l |= 0x8000
self.writeBytes(bytes([(l >> 8) & 0xff, l & 0xff]))
self.write_bytes(bytes([(l >> 8) & 0xff, l & 0xff]))
elif l < 0x200000: elif l < 0x200000:
l |= 0xC00000 l |= 0xC00000
self.writeBytes(bytes([(l >> 16) & 0xff, (l >> 8) & 0xff, l & 0xff]))
self.write_bytes(bytes([(l >> 16) & 0xff, (l >> 8) & 0xff, l & 0xff]))
elif l < 0x10000000: elif l < 0x10000000:
l |= 0xE0000000 l |= 0xE0000000
self.writeBytes(bytes([(l >> 24) & 0xff, (l >> 16) & 0xff, (l >> 8) & 0xff, l & 0xff]))
self.write_bytes(bytes([(l >> 24) & 0xff, (l >> 16) & 0xff, (l >> 8) & 0xff, l & 0xff]))
else: else:
self.writeBytes(bytes([0xf0, (l >> 24) & 0xff, (l >> 16) & 0xff, (l >> 8) & 0xff, l & 0xff]))
self.write_bytes(bytes([0xf0, (l >> 24) & 0xff, (l >> 16) & 0xff, (l >> 8) & 0xff, l & 0xff]))
def readLen(self):
c = self.readBytes(1)[0]
def read_len(self):
c = self.read_bytes(1)[0]
if (c & 0x80) == 0x00: if (c & 0x80) == 0x00:
pass pass
elif (c & 0xC0) == 0x80: elif (c & 0xC0) == 0x80:
c &= ~0xC0 c &= ~0xC0
c <<= 8 c <<= 8
c += self.readBytes(1)[0]
c += self.read_bytes(1)[0]
elif (c & 0xE0) == 0xC0: elif (c & 0xE0) == 0xC0:
c &= ~0xE0 c &= ~0xE0
c <<= 8 c <<= 8
c += self.readBytes(1)[0]
c += self.read_bytes(1)[0]
c <<= 8 c <<= 8
c += self.readBytes(1)[0]
c += self.read_bytes(1)[0]
elif (c & 0xF0) == 0xE0: elif (c & 0xF0) == 0xE0:
c &= ~0xF0 c &= ~0xF0
c <<= 8 c <<= 8
c += self.readBytes(1)[0]
c += self.read_bytes(1)[0]
c <<= 8 c <<= 8
c += self.readBytes(1)[0]
c += self.read_bytes(1)[0]
c <<= 8 c <<= 8
c += self.readBytes(1)[0]
c += self.read_bytes(1)[0]
elif (c & 0xF8) == 0xF0: elif (c & 0xF8) == 0xF0:
c = self.readBytes(1)[0]
c = self.read_bytes(1)[0]
c <<= 8 c <<= 8
c += self.readBytes(1)[0]
c += self.read_bytes(1)[0]
c <<= 8 c <<= 8
c += self.readBytes(1)[0]
c += self.read_bytes(1)[0]
c <<= 8 c <<= 8
c += self.readBytes(1)[0]
c += self.read_bytes(1)[0]
return c return c
def writeBytes(self, s):
def write_bytes(self, s):
n = 0 n = 0
while n < len(s): while n < len(s):
r = self.sk.send(s[n:]) r = self.sk.send(s[n:])
if r == 0: raise NasFailedResult("connection closed by remote end")
if r == 0:
raise NasFailedResult("connection closed by remote end")
n += r n += r
def readBytes(self, length):
def read_bytes(self, length):
ret = b'' ret = b''
while len(ret) < length: while len(ret) < length:
s = self.sk.recv(length - len(ret)) s = self.sk.recv(length - len(ret))
if len(s) == 0: raise NasFailedResult("connection closed by remote end")
if len(s) == 0:
raise NasFailedResult("connection closed by remote end")
ret += s ret += s
return ret return ret
class TransmitterManager(BaseTransmitter, metaclass=ABCMeta): class TransmitterManager(BaseTransmitter, metaclass=ABCMeta):
def __init__(self, login=None, password=None, ip=None, port=None): def __init__(self, login=None, password=None, ip=None, port=None):
ip = ip or getattr(local_settings, 'NAS_IP') ip = ip or getattr(local_settings, 'NAS_IP')
if ip is None:
raise NasNetworkError('Не передан ip адрес NAS')
if ip is None or ip == '<NAS IP>':
raise NasNetworkError('Ip address of NAS does not specified')
if not ping(ip): if not ping(ip):
raise NasNetworkError('NAS %s не пингуется' % ip)
raise NasNetworkError('NAS %(ip_addr)s does not pinged' % {
'ip_addr': ip
})
try: try:
self.ar = ApiRos(ip, port) self.ar = ApiRos(ip, port)
self.ar.login(login or getattr(local_settings, 'NAS_LOGIN'), password or getattr(local_settings, 'NAS_PASSW'))
self.ar.login(login or getattr(local_settings, 'NAS_LOGIN'),
password or getattr(local_settings, 'NAS_PASSW'))
except ConnectionRefusedError: except ConnectionRefusedError:
raise NasNetworkError('Подключение к %s отклонено (Connection Refused)' % ip)
raise NasNetworkError('Connection to %s is Refused' % ip)
def __del__(self): def __del__(self):
if hasattr(self, 's'): if hasattr(self, 's'):
self.s.close() self.s.close()
def _exec_cmd(self, cmd):
def _exec_cmd(self, cmd: list) -> list:
if not isinstance(cmd, list): if not isinstance(cmd, list):
raise TypeError raise TypeError
result_iter = self.ar.talk_iter(cmd) result_iter = self.ar.talk_iter(cmd)
@ -181,7 +192,7 @@ class TransmitterManager(BaseTransmitter, metaclass=ABCMeta):
res.append(rt[1]) res.append(rt[1])
return res return res
def _exec_cmd_iter(self, cmd):
def _exec_cmd_iter(self, cmd: list) -> Iterable:
if not isinstance(cmd, list): if not isinstance(cmd, list):
raise TypeError raise TypeError
result_iter = self.ar.talk_iter(cmd) result_iter = self.ar.talk_iter(cmd)
@ -192,8 +203,9 @@ class TransmitterManager(BaseTransmitter, metaclass=ABCMeta):
raise NasFailedResult(rt[1]['=message']) raise NasFailedResult(rt[1]['=message'])
yield rt yield rt
# Строим объект ShapeItem из инфы, присланной из mikrotik'a
def _build_shape_obj(self, info):
# Build object ShapeItem from information from mikrotik
@staticmethod
def _build_shape_obj(info: dict) -> AbonStruct:
# Переводим приставку скорости Mikrotik в Mbit/s # Переводим приставку скорости Mikrotik в Mbit/s
def parse_speed(text_speed): def parse_speed(text_speed):
text_speed_digit = float(text_speed[:-1] or 0.0) text_speed_digit = float(text_speed[:-1] or 0.0)
@ -228,13 +240,13 @@ class TransmitterManager(BaseTransmitter, metaclass=ABCMeta):
class QueueManager(TransmitterManager, metaclass=ABCMeta): class QueueManager(TransmitterManager, metaclass=ABCMeta):
# ищем правило по имени, и возвращаем всю инфу о найденном правиле
def find(self, name):
# Find queue by name
def find(self, name: str) -> AbonStruct:
ret = self._exec_cmd(['/queue/simple/print', '?name=%s' % name]) ret = self._exec_cmd(['/queue/simple/print', '?name=%s' % name])
if len(ret) > 1: if len(ret) > 1:
return self._build_shape_obj(ret[0]) return self._build_shape_obj(ret[0])
def add(self, user):
def add(self, user: AbonStruct):
if not isinstance(user, AbonStruct): if not isinstance(user, AbonStruct):
raise TypeError raise TypeError
if user.tariff is None or not isinstance(user.tariff, TariffStruct): if user.tariff is None or not isinstance(user.tariff, TariffStruct):
@ -248,32 +260,30 @@ class QueueManager(TransmitterManager, metaclass=ABCMeta):
'=burst-time=1/1' '=burst-time=1/1'
]) ])
def remove(self, user):
def remove(self, user: AbonStruct):
if not isinstance(user, AbonStruct): if not isinstance(user, AbonStruct):
raise TypeError raise TypeError
q = self.find('uid%d' % user.uid) q = self.find('uid%d' % user.uid)
if q is not None: if q is not None:
return self._exec_cmd(['/queue/simple/remove', '=.id=' + getattr(q, 'queue_id', '')]) return self._exec_cmd(['/queue/simple/remove', '=.id=' + getattr(q, 'queue_id', '')])
def remove_range(self, q_ids):
def remove_range(self, q_ids: List[str]):
try: try:
#q_ids = [q.queue_id for q in q_ids]
# q_ids = [q.queue_id for q in q_ids]
return self._exec_cmd(['/queue/simple/remove', '=numbers=' + ','.join(q_ids)]) return self._exec_cmd(['/queue/simple/remove', '=numbers=' + ','.join(q_ids)])
except TypeError as e: except TypeError as e:
print(e) print(e)
def update(self, user):
def update(self, user: AbonStruct):
if not isinstance(user, AbonStruct): if not isinstance(user, AbonStruct):
raise TypeError raise TypeError
if user.tariff is None or not isinstance(user.tariff, TariffStruct): if user.tariff is None or not isinstance(user.tariff, TariffStruct):
return return
queue = self.find('uid%d' % user.uid) queue = self.find('uid%d' % user.uid)
if queue is None: if queue is None:
# не нашли запись в шейпере об абоненте, добавим
return self.add(user) return self.add(user)
else: else:
mk_id = getattr(queue, 'queue_id', '') mk_id = getattr(queue, 'queue_id', '')
# обновляем шейпер абонента
return self._exec_cmd(['/queue/simple/set', '=.id=' + mk_id, return self._exec_cmd(['/queue/simple/set', '=.id=' + mk_id,
'=name=uid%d' % user.uid, '=name=uid%d' % user.uid,
'=max-limit=%.3fM/%.3fM' % (user.tariff.speedOut, user.tariff.speedIn), '=max-limit=%.3fM/%.3fM' % (user.tariff.speedOut, user.tariff.speedIn),
@ -283,7 +293,6 @@ class QueueManager(TransmitterManager, metaclass=ABCMeta):
'=burst-time=1/1' '=burst-time=1/1'
]) ])
# читаем шейпер, возващаем записи о шейпере
def read_queue_iter(self): def read_queue_iter(self):
for code, dat in self._exec_cmd_iter(['/queue/simple/print', '=detail']): for code, dat in self._exec_cmd_iter(['/queue/simple/print', '=detail']):
if code == '!done': if code == '!done':
@ -292,14 +301,14 @@ class QueueManager(TransmitterManager, metaclass=ABCMeta):
if sobj is not None: if sobj is not None:
yield sobj yield sobj
# то же что и выше, только получаем только номера в микротике
def read_mikroids_iter(self): def read_mikroids_iter(self):
queues = self._exec_cmd_iter(['/queue/simple/print', '=detail']) queues = self._exec_cmd_iter(['/queue/simple/print', '=detail'])
for queue in queues: for queue in queues:
if queue[0] == '!done': return
if queue[0] == '!done':
return
yield int(queue[1]['=.id'].replace('*', ''), base=16) yield int(queue[1]['=.id'].replace('*', ''), base=16)
def disable(self, user):
def disable(self, user: AbonStruct):
if not isinstance(user, AbonStruct): if not isinstance(user, AbonStruct):
raise TypeError raise TypeError
q = self.find('uid%d' % user.uid) q = self.find('uid%d' % user.uid)
@ -309,7 +318,7 @@ class QueueManager(TransmitterManager, metaclass=ABCMeta):
else: else:
return self._exec_cmd(['/queue/simple/disable', '=.id=*' + getattr(q, 'queue_id', '')]) return self._exec_cmd(['/queue/simple/disable', '=.id=*' + getattr(q, 'queue_id', '')])
def enable(self, user):
def enable(self, user: AbonStruct):
if not isinstance(user, AbonStruct): if not isinstance(user, AbonStruct):
raise TypeError raise TypeError
q = self.find('uid%d' % user.uid) q = self.find('uid%d' % user.uid)
@ -327,8 +336,7 @@ class IpAddressListObj(IpStruct):
class IpAddressListManager(TransmitterManager, metaclass=ABCMeta): class IpAddressListManager(TransmitterManager, metaclass=ABCMeta):
def add(self, list_name, ip, timeout=None):
def add(self, list_name: str, ip: IpStruct, timeout=0):
if not isinstance(ip, IpStruct): if not isinstance(ip, IpStruct):
raise TypeError raise TypeError
commands = [ commands = [
@ -336,7 +344,7 @@ class IpAddressListManager(TransmitterManager, metaclass=ABCMeta):
'=list=%s' % list_name, '=list=%s' % list_name,
'=address=%s' % str(ip) '=address=%s' % str(ip)
] ]
if type(timeout) is int:
if timeout > 0:
commands.append('=timeout=%d' % timeout) commands.append('=timeout=%d' % timeout)
return self._exec_cmd(commands) return self._exec_cmd(commands)
@ -354,7 +362,7 @@ class IpAddressListManager(TransmitterManager, metaclass=ABCMeta):
'=.id=*' + str(mk_id).replace('*', '') '=.id=*' + str(mk_id).replace('*', '')
]) ])
def remove_range(self, items):
def remove_range(self, items: Iterable[IpAddressListObj]):
ids = [ip.mk_id for ip in items if isinstance(ip, IpAddressListObj)] ids = [ip.mk_id for ip in items if isinstance(ip, IpAddressListObj)]
if len(ids) > 0: if len(ids) > 0:
return self._exec_cmd([ return self._exec_cmd([
@ -362,7 +370,7 @@ class IpAddressListManager(TransmitterManager, metaclass=ABCMeta):
'=numbers=*%s' % ',*'.join(ids) '=numbers=*%s' % ',*'.join(ids)
]) ])
def find(self, ip, list_name):
def find(self, ip: IpStruct, list_name: str):
if not isinstance(ip, IpStruct): if not isinstance(ip, IpStruct):
raise TypeError raise TypeError
return self._exec_cmd([ return self._exec_cmd([
@ -371,8 +379,8 @@ class IpAddressListManager(TransmitterManager, metaclass=ABCMeta):
'?address=%s' % str(ip) '?address=%s' % str(ip)
]) ])
def read_ips_iter(self, list_name):
ips = self._exec_cmd_iter([
def read_ips_iter(self, list_name: str):
ips: Iterable = self._exec_cmd_iter([
'/ip/firewall/address-list/print', 'where', '/ip/firewall/address-list/print', 'where',
'?list=%s' % list_name, '?list=%s' % list_name,
'?dynamic=no' '?dynamic=no'
@ -381,7 +389,7 @@ class IpAddressListManager(TransmitterManager, metaclass=ABCMeta):
if dat != {}: if dat != {}:
yield IpAddressListObj(dat['=address'], dat['=.id']) yield IpAddressListObj(dat['=address'], dat['=.id'])
def disable(self, user):
def disable(self, user: AbonStruct):
r = IpAddressListManager.find(self, user.ip, LIST_USERS_ALLOWED) r = IpAddressListManager.find(self, user.ip, LIST_USERS_ALLOWED)
if len(r) > 1: if len(r) > 1:
mk_id = r[0]['=.id'] mk_id = r[0]['=.id']
@ -401,13 +409,12 @@ class IpAddressListManager(TransmitterManager, metaclass=ABCMeta):
class MikrotikTransmitter(QueueManager, IpAddressListManager): class MikrotikTransmitter(QueueManager, IpAddressListManager):
def add_user_range(self, user_list):
def add_user_range(self, user_list: VectorAbon):
super(MikrotikTransmitter, self).add_user_range(user_list) super(MikrotikTransmitter, self).add_user_range(user_list)
for usr in user_list: for usr in user_list:
self.add_user(usr) self.add_user(usr)
def remove_user_range(self, users):
def remove_user_range(self, users: VectorAbon):
super(MikrotikTransmitter, self).remove_user_range(users) super(MikrotikTransmitter, self).remove_user_range(users)
queue_ids = [usr.queue_id for usr in users if usr is not None] queue_ids = [usr.queue_id for usr in users if usr is not None]
QueueManager.remove_range(self, queue_ids) QueueManager.remove_range(self, queue_ids)
@ -417,7 +424,7 @@ class MikrotikTransmitter(QueueManager, IpAddressListManager):
if ip_list_entity is not None and len(ip_list_entity) > 1: if ip_list_entity is not None and len(ip_list_entity) > 1:
IpAddressListManager.remove(self, ip_list_entity[0]['=.id']) IpAddressListManager.remove(self, ip_list_entity[0]['=.id'])
def add_user(self, user, ip_timeout=None):
def add_user(self, user: AbonStruct, ip_timeout=None):
super(MikrotikTransmitter, self).add_user(user, ip_timeout) super(MikrotikTransmitter, self).add_user(user, ip_timeout)
if not isinstance(user.ip, IpStruct): if not isinstance(user.ip, IpStruct):
raise TypeError raise TypeError
@ -430,7 +437,7 @@ class MikrotikTransmitter(QueueManager, IpAddressListManager):
if len(firewall_ip_list_obj) > 1: if len(firewall_ip_list_obj) > 1:
IpAddressListManager.remove(self, firewall_ip_list_obj[0]['=.id']) IpAddressListManager.remove(self, firewall_ip_list_obj[0]['=.id'])
def remove_user(self, user):
def remove_user(self, user: AbonStruct):
super(MikrotikTransmitter, self).remove_user(user) super(MikrotikTransmitter, self).remove_user(user)
QueueManager.remove(self, user) QueueManager.remove(self, user)
firewall_ip_list_obj = IpAddressListManager.find(self, user.ip, LIST_USERS_ALLOWED) firewall_ip_list_obj = IpAddressListManager.find(self, user.ip, LIST_USERS_ALLOWED)
@ -438,7 +445,7 @@ class MikrotikTransmitter(QueueManager, IpAddressListManager):
IpAddressListManager.remove(self, firewall_ip_list_obj[0]['=.id']) IpAddressListManager.remove(self, firewall_ip_list_obj[0]['=.id'])
# обновляем основную инфу абонента # обновляем основную инфу абонента
def update_user(self, user, ip_timeout=None):
def update_user(self, user: AbonStruct, ip_timeout=None):
super(MikrotikTransmitter, self).update_user(user, ip_timeout) super(MikrotikTransmitter, self).update_user(user, ip_timeout)
if not isinstance(user.ip, IpStruct): if not isinstance(user.ip, IpStruct):
raise TypeError raise TypeError
@ -495,22 +502,22 @@ class MikrotikTransmitter(QueueManager, IpAddressListManager):
return received, sent return received, sent
# Тарифы хранить нам не надо, так что методы тарифов ниже не реализуем # Тарифы хранить нам не надо, так что методы тарифов ниже не реализуем
def add_tariff_range(self, tariff_list):
def add_tariff_range(self, tariff_list: VectorTariff):
pass pass
# соответственно и удалять тарифы не надо # соответственно и удалять тарифы не надо
def remove_tariff_range(self, tariff_list):
def remove_tariff_range(self, tariff_list: VectorTariff):
pass pass
# и добавлять тоже # и добавлять тоже
def add_tariff(self, tariff):
def add_tariff(self, tariff: TariffStruct):
pass pass
# и обновлять # и обновлять
def update_tariff(self, tariff):
def update_tariff(self, tariff: TariffStruct):
pass pass
def remove_tariff(self, tid):
def remove_tariff(self, tid: int):
pass pass
def read_users(self): def read_users(self):

14
agent/settings.py.example

@ -1,16 +1,12 @@
# -*- coding: utf-8 -*-
# Для установки модуля NAS смотрите в __init__
# Setting NAS module in __init__.py
# Certificates # Certificates
CERTFILE = "/etc/ssl/server.crt"
KEYFILE = "/etc/ssl/server.key"
#CERTFILE = "/etc/ssl/server.crt"
#KEYFILE = "/etc/ssl/server.key"
# Использовать-ли при передаче инфы между NAS и основным сервером SSL
IS_USE_SSL = False
#IS_USE_SSL = False
# Адрес, порт, логин и пароль сервера NAS
# information for access on NAS server
NAS_IP = '<NAS IP>' NAS_IP = '<NAS IP>'
NAS_LOGIN = 'admin' NAS_LOGIN = 'admin'
NAS_PASSW = '<PASSWORD>' NAS_PASSW = '<PASSWORD>'

5
agent/structs.py

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from abc import ABCMeta, abstractmethod from abc import ABCMeta, abstractmethod
from struct import pack, unpack, calcsize from struct import pack, unpack, calcsize
from typing import Iterable
from .utils import int2ip, ip2int from .utils import int2ip, ip2int
@ -155,3 +156,7 @@ class ShapeItem(BaseStruct):
if not isinstance(other, ShapeItem): if not isinstance(other, ShapeItem):
raise TypeError raise TypeError
return self.sid == other.sid and self.abon == other.abon return self.sid == other.sid and self.abon == other.abon
VectorAbon = Iterable[AbonStruct]
VectorTariff = Iterable[TariffStruct]

41
docs/dev.md

@ -159,76 +159,63 @@ from .core import BaseTransmitter, NasFailedResult, NasNetworkError
class LinuxTransmitter(BaseTransmitter): class LinuxTransmitter(BaseTransmitter):
def add_user_range(self, user_list):
@abstractmethod
def add_user_range(self, user_list: VectorAbon):
"""добавляем список абонентов в NAS""" """добавляем список абонентов в NAS"""
@abstractmethod @abstractmethod
@check_input_type(AbonStruct)
def remove_user_range(self, users):
def remove_user_range(self, users: VectorAbon):
"""удаляем список абонентов""" """удаляем список абонентов"""
@abstractmethod @abstractmethod
@check_input_type(AbonStruct)
def add_user(self, user, *args):
def add_user(self, user: AbonStruct, *args):
"""добавляем абонента""" """добавляем абонента"""
@abstractmethod @abstractmethod
@check_input_type(AbonStruct)
def remove_user(self, user):
def remove_user(self, user: AbonStruct):
"""удаляем абонента""" """удаляем абонента"""
@abstractmethod @abstractmethod
@check_input_type(AbonStruct)
def update_user(self, user, *args):
"""
Чтоб обновить абонента можно изменить всё кроме его uid, по uid абонент будет найден.
Это значит что вы можете передать объект user класса AbonStruct, где только uid будет указывать на абонента,
а остальные поля будут содержать новое значение.
"""
def update_user(self, user: AbonStruct, *args):
"""чтоб обновить абонента можно изменить всё кроме его uid, по uid абонент будет найден"""
@abstractmethod @abstractmethod
@check_input_type(TariffStruct)
def add_tariff_range(self, tariff_list):
def add_tariff_range(self, tariff_list: VectorTariff):
""" """
Пока не используется, зарезервировано. Пока не используется, зарезервировано.
Добавляет список тарифов в NAS Добавляет список тарифов в NAS
""" """
@abstractmethod @abstractmethod
@check_input_type(TariffStruct)
def remove_tariff_range(self, tariff_list):
def remove_tariff_range(self, tariff_list: VectorTariff):
""" """
Пока не используется, зарезервировано. Пока не используется, зарезервировано.
Удаляем список тарифов по уникальным идентификаторам Удаляем список тарифов по уникальным идентификаторам
""" """
@abstractmethod @abstractmethod
@check_input_type(TariffStruct)
def add_tariff(self, tariff):
def add_tariff(self, tariff: TariffStruct):
""" """
Пока не используется, зарезервировано. Пока не используется, зарезервировано.
Добавляем тариф
Добавляет тариф
""" """
@abstractmethod @abstractmethod
@check_input_type(TariffStruct)
def update_tariff(self, tariff):
def update_tariff(self, tariff: TariffStruct):
""" """
Пока не используется, зарезервировано. Пока не используется, зарезервировано.
Чтоб обновить тариф надо изменить всё кроме его tid, по tid тариф будет найден Чтоб обновить тариф надо изменить всё кроме его tid, по tid тариф будет найден
""" """
@abstractmethod @abstractmethod
@check_input_type(TariffStruct)
def remove_tariff(self, tid):
def remove_tariff(self, tid: int):
""" """
:param tid: id тарифа в среде NAS сервера чтоб удалить по этому номеру :param tid: id тарифа в среде NAS сервера чтоб удалить по этому номеру
Пока не используется, зарезервировано. Пока не используется, зарезервировано.
""" """
@abstractmethod @abstractmethod
@check_input_type(TariffStruct)
def ping(self, host, count=10):
def ping(self, host: str, count=10):
""" """
:param host: ip адрес в текстовом виде, например '192.168.0.1' :param host: ip адрес в текстовом виде, например '192.168.0.1'
:param count: количество пингов :param count: количество пингов

2
mydefs.py

@ -124,7 +124,7 @@ def only_admins(fn):
return wrapped return wrapped
def ping(hostname, count=1):
def ping(hostname: str, count=1):
response = os.system("`which ping` -4Anq -c%d -W1 %s > /dev/null" % (count, hostname)) response = os.system("`which ping` -4Anq -c%d -W1 %s > /dev/null" % (count, hostname))
return True if response == 0 else False return True if response == 0 else False

23
tariff_app/base_intr.py

@ -1,20 +1,21 @@
# -*- coding: utf-8 -*-
from abc import ABCMeta, abstractmethod from abc import ABCMeta, abstractmethod
from datetime import datetime
from typing import AnyStr, Optional, Union
class TariffBase(metaclass=ABCMeta): class TariffBase(metaclass=ABCMeta):
@abstractmethod @abstractmethod
def calc_amount(self):
def calc_amount(self) -> float:
"""Calculates total amount of payment""" """Calculates total amount of payment"""
raise NotImplementedError raise NotImplementedError
@abstractmethod @abstractmethod
def calc_deadline(self):
def calc_deadline(self) -> datetime:
"""Calculate deadline date""" """Calculate deadline date"""
raise NotImplementedError raise NotImplementedError
@staticmethod @staticmethod
def description():
def description() -> AnyStr:
""" """
Usage in mydefs.MyChoicesAdapter for choices fields. Usage in mydefs.MyChoicesAdapter for choices fields.
:return: human readable description :return: human readable description
@ -22,19 +23,19 @@ class TariffBase(metaclass=ABCMeta):
raise NotImplementedError raise NotImplementedError
@staticmethod @staticmethod
def manage_access(abon):
def manage_access(abon) -> bool:
"""Manage subscribers access to service""" """Manage subscribers access to service"""
#assert isinstance(abon, Abon)
if not abon.is_active: return False
if not abon.is_active:
return False
act_tar = abon.active_tariff() act_tar = abon.active_tariff()
if act_tar: if act_tar:
return True return True
return False
class PeriodicPayCalcBase(metaclass=ABCMeta): class PeriodicPayCalcBase(metaclass=ABCMeta):
@abstractmethod @abstractmethod
def calc_amount(self, model_object):
def calc_amount(self, model_object) -> float:
""" """
:param model_object: it is a instance of models.PeriodicPay model :param model_object: it is a instance of models.PeriodicPay model
:return: float: amount for the service :return: float: amount for the service
@ -42,7 +43,7 @@ class PeriodicPayCalcBase(metaclass=ABCMeta):
raise NotImplementedError raise NotImplementedError
@abstractmethod @abstractmethod
def get_next_time_to_pay(self, model_object, last_time_payment):
def get_next_time_to_pay(self, model_object, last_time_payment: Optional[Union[datetime, None]]) -> datetime:
""" """
:param model_object: it is a instance of models.PeriodicPay model :param model_object: it is a instance of models.PeriodicPay model
:param last_time_payment: May be None if first pay :param last_time_payment: May be None if first pay
@ -51,7 +52,7 @@ class PeriodicPayCalcBase(metaclass=ABCMeta):
raise NotImplementedError raise NotImplementedError
@staticmethod @staticmethod
def description():
def description() -> AnyStr:
"""Return text description. """Return text description.
Uses in mydefs.MyChoicesAdapter for CHOICES fields""" Uses in mydefs.MyChoicesAdapter for CHOICES fields"""
raise NotImplementedError raise NotImplementedError

28
tariff_app/custom_tariffs.py

@ -1,7 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from datetime import timedelta, datetime from datetime import timedelta, datetime
from typing import AnyStr
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext as _
from django.utils.translation import gettext as _
from .base_intr import TariffBase, PeriodicPayCalcBase from .base_intr import TariffBase, PeriodicPayCalcBase
from calendar import monthrange from calendar import monthrange
@ -14,7 +16,7 @@ class TariffDefault(TariffBase):
self.abon_tariff = abon_tariff self.abon_tariff = abon_tariff
# Базовый функционал считает стоимость пропорционально использованному времени # Базовый функционал считает стоимость пропорционально использованному времени
def calc_amount(self):
def calc_amount(self) -> float:
# сейчас # сейчас
nw = timezone.now() nw = timezone.now()
@ -35,7 +37,7 @@ class TariffDefault(TariffBase):
return float(res) return float(res)
# Тут мы расчитываем конец действия услуги, завершение будет в конце месяца # Тут мы расчитываем конец действия услуги, завершение будет в конце месяца
def calc_deadline(self):
def calc_deadline(self) -> datetime:
nw = timezone.now() nw = timezone.now()
last_day = monthrange(nw.year, nw.month)[1] last_day = monthrange(nw.year, nw.month)[1]
last_month_date = datetime(year=nw.year, month=nw.month, day=last_day, last_month_date = datetime(year=nw.year, month=nw.month, day=last_day,
@ -43,7 +45,7 @@ class TariffDefault(TariffBase):
return last_month_date return last_month_date
@staticmethod @staticmethod
def description():
def description() -> AnyStr:
return _('Base calculate functionality') return _('Base calculate functionality')
@ -51,17 +53,17 @@ class TariffDp(TariffDefault):
# в IS снимается вся стоимость тарифа вне зависимости от времени использования # в IS снимается вся стоимость тарифа вне зависимости от времени использования
# просто возвращаем всю стоимость тарифа # просто возвращаем всю стоимость тарифа
def calc_amount(self):
def calc_amount(self) -> float:
return float(self.abon_tariff.tariff.amount) return float(self.abon_tariff.tariff.amount)
@staticmethod @staticmethod
def description():
def description() -> AnyStr:
return 'IS' return 'IS'
# Как в IS только не на время, а на 10 лет # Как в IS только не на время, а на 10 лет
class TariffCp(TariffDp): class TariffCp(TariffDp):
def calc_deadline(self):
def calc_deadline(self) -> datetime:
# делаем время окончания услуги на 10 лет вперёд # делаем время окончания услуги на 10 лет вперёд
nw = timezone.now() nw = timezone.now()
long_long_time = datetime(year=nw.year + 10, month=nw.month, day=nw.day, long_long_time = datetime(year=nw.year + 10, month=nw.month, day=nw.day,
@ -69,7 +71,7 @@ class TariffCp(TariffDp):
return long_long_time return long_long_time
@staticmethod @staticmethod
def description():
def description() -> AnyStr:
return _('Private service') return _('Private service')
@ -82,20 +84,20 @@ TARIFF_CHOICES = (
class PeriodicPayCalcDefault(PeriodicPayCalcBase): class PeriodicPayCalcDefault(PeriodicPayCalcBase):
def calc_amount(self, model_object):
def calc_amount(self, model_object) -> float:
return model_object.amount return model_object.amount
def get_next_time_to_pay(self, model_object, last_time_payment):
def get_next_time_to_pay(self, model_object, last_time_payment) -> datetime:
# TODO: решить какой будет расёт периодических платежей # TODO: решить какой будет расёт периодических платежей
return datetime.now() + timedelta(days=30) return datetime.now() + timedelta(days=30)
@staticmethod @staticmethod
def description():
def description() -> AnyStr:
return _('Default periodic pay') return _('Default periodic pay')
class PeriodicPayCalcCustom(PeriodicPayCalcDefault): class PeriodicPayCalcCustom(PeriodicPayCalcDefault):
def calc_amount(self, model_object):
def calc_amount(self, model_object) -> float:
""" """
:param model_object: it is a instance of models.PeriodicPay model :param model_object: it is a instance of models.PeriodicPay model
:return: float: amount for the service :return: float: amount for the service
@ -103,7 +105,7 @@ class PeriodicPayCalcCustom(PeriodicPayCalcDefault):
return uniform(1, 10) return uniform(1, 10)
@staticmethod @staticmethod
def description():
def description() -> AnyStr:
return _('Custom periodic pay') return _('Custom periodic pay')

2
taskapp/context_proc.py

@ -1,5 +1,3 @@
from django.contrib.auth.models import AnonymousUser
from taskapp.models import Task from taskapp.models import Task
from accounts_app.models import UserProfile from accounts_app.models import UserProfile

2
taskapp/forms.py

@ -45,7 +45,7 @@ class TaskFrm(forms.ModelForm):
class ExtraCommentForm(forms.ModelForm): class ExtraCommentForm(forms.ModelForm):
def make_save(self, author, task):
def make_save(self, author, task: Task):
comment = super(ExtraCommentForm, self).save(commit=False) comment = super(ExtraCommentForm, self).save(commit=False)
comment.author = author comment.author = author
comment.task = task comment.task = task

Loading…
Cancel
Save