From 4d6995845e50c72a369354689e8748db7406bbf0 Mon Sep 17 00:00:00 2001 From: bashmak Date: Sat, 24 Mar 2018 14:07:06 +0300 Subject: [PATCH] fix bug --- abonapp/models.py | 5 +- abonapp/views.py | 6 +- agent/core.py | 63 ++++-------- agent/mod_mikrotik.py | 183 ++++++++++++++++++----------------- agent/settings.py.example | 14 +-- agent/structs.py | 5 + docs/dev.md | 41 +++----- mydefs.py | 2 +- tariff_app/base_intr.py | 23 ++--- tariff_app/custom_tariffs.py | 28 +++--- taskapp/context_proc.py | 2 - taskapp/forms.py | 2 +- 12 files changed, 175 insertions(+), 199 deletions(-) diff --git a/abonapp/models.py b/abonapp/models.py index 43a9331..94d1faa 100644 --- a/abonapp/models.py +++ b/abonapp/models.py @@ -1,4 +1,5 @@ from datetime import datetime +from typing import Optional, Union from django.conf import settings from django.core import validators @@ -277,7 +278,7 @@ class Abon(BaseAccount): raise LogicError(_('Ip address already exist')) 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 if hasattr(self, 'is_dhcp') and self.is_dhcp: timeout = getattr(settings, 'DHCP_TIMEOUT', 14400) @@ -292,7 +293,7 @@ class Abon(BaseAccount): tm.update_user(agent_abon, ip_timeout=timeout) except (NasFailedResult, NasNetworkError, ConnectionResetError) as e: print('ERROR:', e) - return True + return e class PassportInfo(models.Model): diff --git a/abonapp/views.py b/abonapp/views.py index 35792d0..d795a54 100644 --- a/abonapp/views.py +++ b/abonapp/views.py @@ -6,7 +6,7 @@ from django.db import IntegrityError, ProgrammingError, transaction from django.db.models import Count, Q from django.shortcuts import render, redirect, get_object_or_404, resolve_url from django.contrib.auth.decorators import login_required -from django.http import HttpResponse, Http404 +from django.http import HttpResponse, HttpResponseBadRequest from django.contrib import messages from django.utils.translation import gettext_lazy as _ from django.utils.decorators import method_decorator @@ -273,7 +273,9 @@ def abonhome(request, gid, uid): if newip: abon.ip_address = newip 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')) else: messages.warning(request, _('fix form errors')) diff --git a/agent/core.py b/agent/core.py index 3b04294..6346917 100644 --- a/agent/core.py +++ b/agent/core.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- from abc import ABCMeta, abstractmethod -from .structs import AbonStruct, TariffStruct +from typing import Iterator, Any, Tuple + +from .structs import AbonStruct, TariffStruct, VectorAbon, VectorTariff # Всплывает если из NAS вернулся не удачный результат @@ -13,90 +15,65 @@ class NasNetworkError(Exception): 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'ом class BaseTransmitter(metaclass=ABCMeta): @abstractmethod - @check_input_type(set) - def add_user_range(self, user_list): + def add_user_range(self, user_list: VectorAbon): """добавляем список абонентов в NAS""" @abstractmethod - @check_input_type(set) - def remove_user_range(self, users): + def remove_user_range(self, users: VectorAbon): """удаляем список абонентов""" @abstractmethod - @check_input_type(AbonStruct) - def add_user(self, user, *args): + def add_user(self, user: AbonStruct, *args): """добавляем абонента""" @abstractmethod - @check_input_type(AbonStruct) - def remove_user(self, user): + def remove_user(self, user: AbonStruct): """удаляем абонента""" @abstractmethod - @check_input_type(AbonStruct) - def update_user(self, user, *args): + def update_user(self, user: AbonStruct, *args): """чтоб обновить абонента можно изменить всё кроме его uid, по uid абонент будет найден""" @abstractmethod - @check_input_type(TariffStruct) - def add_tariff_range(self, tariff_list): + def add_tariff_range(self, tariff_list: VectorTariff): """ Пока не используется, зарезервировано. Добавляет список тарифов в NAS """ @abstractmethod - @check_input_type(TariffStruct) - def remove_tariff_range(self, tariff_list): + def remove_tariff_range(self, tariff_list: VectorTariff): """ Пока не используется, зарезервировано. Удаляем список тарифов по уникальным идентификаторам """ @abstractmethod - @check_input_type(TariffStruct) - def add_tariff(self, tariff): + def add_tariff(self, tariff: TariffStruct): """ Пока не используется, зарезервировано. Добавляет тариф """ @abstractmethod - @check_input_type(TariffStruct) - def update_tariff(self, tariff): + def update_tariff(self, tariff: TariffStruct): """ Пока не используется, зарезервировано. Чтоб обновить тариф надо изменить всё кроме его tid, по tid тариф будет найден """ @abstractmethod - @check_input_type(TariffStruct) - def remove_tariff(self, tid): + def remove_tariff(self, tid: int): """ :param tid: id тарифа в среде NAS сервера чтоб удалить по этому номеру Пока не используется, зарезервировано. """ @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 count: количество пингов @@ -110,19 +87,19 @@ class BaseTransmitter(metaclass=ABCMeta): :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 всех абонентов у которых может быть обслуживание :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()) - 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 - 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) if len(list_for_del) > 0: print('FOR DELETE') diff --git a/agent/mod_mikrotik.py b/agent/mod_mikrotik.py index d78c6d1..6798a37 100644 --- a/agent/mod_mikrotik.py +++ b/agent/mod_mikrotik.py @@ -3,14 +3,14 @@ import socket import binascii from abc import ABCMeta from hashlib import md5 +from typing import List, Iterable from .core import BaseTransmitter, NasFailedResult, NasNetworkError 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 django.conf import settings import re - DEBUG = getattr(settings, 'DEBUG', False) LIST_USERS_ALLOWED = 'DjingUsersAllowed' @@ -19,14 +19,16 @@ LIST_USERS_BLOCKED = 'DjingUsersBlocked' @singleton class ApiRos: - "Routeros api" + """Routeros api""" sk = None is_login = False - def __init__(self, ip, port): + def __init__(self, ip: str, port: int): if self.sk is None: 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.currenttag = 0 @@ -41,136 +43,145 @@ class ApiRos: md.update(b'\x00') md.update(bytes(pwd, 'utf-8')) 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 def talk_iter(self, words): - if self.writeSentence(words) == 0: return + if self.write_sentence(words) == 0: + return while 1: - i = self.readSentence() - if len(i) == 0: continue + i = self.read_sentence() + if len(i) == 0: + continue reply = i[0] attrs = {} for w in i[1:]: j = w.find('=', 1) - if (j == -1): + if j == -1: attrs[w] = '' else: attrs[w[:j]] = w[j + 1:] yield (reply, attrs) - if reply == '!done': return + if reply == '!done': + return - def writeSentence(self, words): + def write_sentence(self, words): ret = 0 for w in words: - self.writeWord(w) + self.write_word(w) ret += 1 - self.writeWord('') + self.write_word('') return ret - def readSentence(self): + def read_sentence(self): r = [] while 1: - w = self.readWord() - if w == '': return r + w = self.read_word() + if w == '': + return r r.append(w) - def writeWord(self, w): + def write_word(self, w): if DEBUG: print("<<< " + w) 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: print(">>> " + ret) return ret - def writeLen(self, l): + def write_len(self, l): if l < 0x80: - self.writeBytes(bytes([l])) + self.write_bytes(bytes([l])) elif l < 0x4000: l |= 0x8000 - self.writeBytes(bytes([(l >> 8) & 0xff, l & 0xff])) + self.write_bytes(bytes([(l >> 8) & 0xff, l & 0xff])) elif l < 0x200000: 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: 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: - 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: pass elif (c & 0xC0) == 0x80: c &= ~0xC0 c <<= 8 - c += self.readBytes(1)[0] + c += self.read_bytes(1)[0] elif (c & 0xE0) == 0xC0: c &= ~0xE0 c <<= 8 - c += self.readBytes(1)[0] + c += self.read_bytes(1)[0] c <<= 8 - c += self.readBytes(1)[0] + c += self.read_bytes(1)[0] elif (c & 0xF0) == 0xE0: c &= ~0xF0 c <<= 8 - c += self.readBytes(1)[0] + c += self.read_bytes(1)[0] c <<= 8 - c += self.readBytes(1)[0] + c += self.read_bytes(1)[0] c <<= 8 - c += self.readBytes(1)[0] + c += self.read_bytes(1)[0] elif (c & 0xF8) == 0xF0: - c = self.readBytes(1)[0] + c = self.read_bytes(1)[0] c <<= 8 - c += self.readBytes(1)[0] + c += self.read_bytes(1)[0] c <<= 8 - c += self.readBytes(1)[0] + c += self.read_bytes(1)[0] c <<= 8 - c += self.readBytes(1)[0] + c += self.read_bytes(1)[0] return c - def writeBytes(self, s): + def write_bytes(self, s): n = 0 while n < len(s): 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 - def readBytes(self, length): + def read_bytes(self, length): ret = b'' while len(ret) < length: 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 return ret class TransmitterManager(BaseTransmitter, metaclass=ABCMeta): - def __init__(self, login=None, password=None, ip=None, port=None): ip = ip or getattr(local_settings, 'NAS_IP') - if ip is None: - raise NasNetworkError('Не передан ip адрес NAS') + if ip is None or ip == '': + raise NasNetworkError('Ip address of NAS does not specified') if not ping(ip): - raise NasNetworkError('NAS %s не пингуется' % ip) + raise NasNetworkError('NAS %(ip_addr)s does not pinged' % { + 'ip_addr': ip + }) try: 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: - raise NasNetworkError('Подключение к %s отклонено (Connection Refused)' % ip) + raise NasNetworkError('Connection to %s is Refused' % ip) def __del__(self): if hasattr(self, 's'): self.s.close() - def _exec_cmd(self, cmd): + def _exec_cmd(self, cmd: list) -> list: if not isinstance(cmd, list): raise TypeError result_iter = self.ar.talk_iter(cmd) @@ -181,7 +192,7 @@ class TransmitterManager(BaseTransmitter, metaclass=ABCMeta): res.append(rt[1]) return res - def _exec_cmd_iter(self, cmd): + def _exec_cmd_iter(self, cmd: list) -> Iterable: if not isinstance(cmd, list): raise TypeError result_iter = self.ar.talk_iter(cmd) @@ -192,8 +203,9 @@ class TransmitterManager(BaseTransmitter, metaclass=ABCMeta): raise NasFailedResult(rt[1]['=message']) 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 def parse_speed(text_speed): text_speed_digit = float(text_speed[:-1] or 0.0) @@ -228,13 +240,13 @@ class TransmitterManager(BaseTransmitter, 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]) if len(ret) > 1: return self._build_shape_obj(ret[0]) - def add(self, user): + def add(self, user: AbonStruct): if not isinstance(user, AbonStruct): raise TypeError if user.tariff is None or not isinstance(user.tariff, TariffStruct): @@ -248,32 +260,30 @@ class QueueManager(TransmitterManager, metaclass=ABCMeta): '=burst-time=1/1' ]) - def remove(self, user): + def remove(self, user: AbonStruct): if not isinstance(user, AbonStruct): raise TypeError q = self.find('uid%d' % user.uid) if q is not None: 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: - #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)]) except TypeError as e: print(e) - def update(self, user): + def update(self, user: AbonStruct): if not isinstance(user, AbonStruct): raise TypeError if user.tariff is None or not isinstance(user.tariff, TariffStruct): return queue = self.find('uid%d' % user.uid) if queue is None: - # не нашли запись в шейпере об абоненте, добавим return self.add(user) else: mk_id = getattr(queue, 'queue_id', '') - # обновляем шейпер абонента return self._exec_cmd(['/queue/simple/set', '=.id=' + mk_id, '=name=uid%d' % user.uid, '=max-limit=%.3fM/%.3fM' % (user.tariff.speedOut, user.tariff.speedIn), @@ -283,7 +293,6 @@ class QueueManager(TransmitterManager, metaclass=ABCMeta): '=burst-time=1/1' ]) - # читаем шейпер, возващаем записи о шейпере def read_queue_iter(self): for code, dat in self._exec_cmd_iter(['/queue/simple/print', '=detail']): if code == '!done': @@ -292,14 +301,14 @@ class QueueManager(TransmitterManager, metaclass=ABCMeta): if sobj is not None: yield sobj - # то же что и выше, только получаем только номера в микротике def read_mikroids_iter(self): queues = self._exec_cmd_iter(['/queue/simple/print', '=detail']) for queue in queues: - if queue[0] == '!done': return + if queue[0] == '!done': + return yield int(queue[1]['=.id'].replace('*', ''), base=16) - def disable(self, user): + def disable(self, user: AbonStruct): if not isinstance(user, AbonStruct): raise TypeError q = self.find('uid%d' % user.uid) @@ -309,7 +318,7 @@ class QueueManager(TransmitterManager, metaclass=ABCMeta): else: 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): raise TypeError q = self.find('uid%d' % user.uid) @@ -327,8 +336,7 @@ class IpAddressListObj(IpStruct): 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): raise TypeError commands = [ @@ -336,7 +344,7 @@ class IpAddressListManager(TransmitterManager, metaclass=ABCMeta): '=list=%s' % list_name, '=address=%s' % str(ip) ] - if type(timeout) is int: + if timeout > 0: commands.append('=timeout=%d' % timeout) return self._exec_cmd(commands) @@ -354,7 +362,7 @@ class IpAddressListManager(TransmitterManager, metaclass=ABCMeta): '=.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)] if len(ids) > 0: return self._exec_cmd([ @@ -362,7 +370,7 @@ class IpAddressListManager(TransmitterManager, metaclass=ABCMeta): '=numbers=*%s' % ',*'.join(ids) ]) - def find(self, ip, list_name): + def find(self, ip: IpStruct, list_name: str): if not isinstance(ip, IpStruct): raise TypeError return self._exec_cmd([ @@ -371,8 +379,8 @@ class IpAddressListManager(TransmitterManager, metaclass=ABCMeta): '?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', '?list=%s' % list_name, '?dynamic=no' @@ -381,7 +389,7 @@ class IpAddressListManager(TransmitterManager, metaclass=ABCMeta): if dat != {}: yield IpAddressListObj(dat['=address'], dat['=.id']) - def disable(self, user): + def disable(self, user: AbonStruct): r = IpAddressListManager.find(self, user.ip, LIST_USERS_ALLOWED) if len(r) > 1: mk_id = r[0]['=.id'] @@ -401,13 +409,12 @@ class IpAddressListManager(TransmitterManager, metaclass=ABCMeta): 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) for usr in user_list: self.add_user(usr) - def remove_user_range(self, users): + def remove_user_range(self, users: VectorAbon): super(MikrotikTransmitter, self).remove_user_range(users) queue_ids = [usr.queue_id for usr in users if usr is not None] 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: 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) if not isinstance(user.ip, IpStruct): raise TypeError @@ -430,7 +437,7 @@ class MikrotikTransmitter(QueueManager, IpAddressListManager): if len(firewall_ip_list_obj) > 1: 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) QueueManager.remove(self, user) 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']) # обновляем основную инфу абонента - 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) if not isinstance(user.ip, IpStruct): raise TypeError @@ -495,22 +502,22 @@ class MikrotikTransmitter(QueueManager, IpAddressListManager): return received, sent # Тарифы хранить нам не надо, так что методы тарифов ниже не реализуем - def add_tariff_range(self, tariff_list): + def add_tariff_range(self, tariff_list: VectorTariff): pass # соответственно и удалять тарифы не надо - def remove_tariff_range(self, tariff_list): + def remove_tariff_range(self, tariff_list: VectorTariff): pass # и добавлять тоже - def add_tariff(self, tariff): + def add_tariff(self, tariff: TariffStruct): pass # и обновлять - def update_tariff(self, tariff): + def update_tariff(self, tariff: TariffStruct): pass - def remove_tariff(self, tid): + def remove_tariff(self, tid: int): pass def read_users(self): diff --git a/agent/settings.py.example b/agent/settings.py.example index 13712fe..97b06a4 100644 --- a/agent/settings.py.example +++ b/agent/settings.py.example @@ -1,16 +1,12 @@ -# -*- coding: utf-8 -*- - - -# Для установки модуля NAS смотрите в __init__ +# Setting NAS module in __init__.py # 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_LOGIN = 'admin' NAS_PASSW = '' diff --git a/agent/structs.py b/agent/structs.py index e68a645..b1f6d80 100644 --- a/agent/structs.py +++ b/agent/structs.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from abc import ABCMeta, abstractmethod from struct import pack, unpack, calcsize +from typing import Iterable from .utils import int2ip, ip2int @@ -155,3 +156,7 @@ class ShapeItem(BaseStruct): if not isinstance(other, ShapeItem): raise TypeError return self.sid == other.sid and self.abon == other.abon + + +VectorAbon = Iterable[AbonStruct] +VectorTariff = Iterable[TariffStruct] diff --git a/docs/dev.md b/docs/dev.md index dde6393..f1f47fa 100644 --- a/docs/dev.md +++ b/docs/dev.md @@ -159,76 +159,63 @@ from .core import BaseTransmitter, NasFailedResult, NasNetworkError class LinuxTransmitter(BaseTransmitter): - def add_user_range(self, user_list): + @abstractmethod + def add_user_range(self, user_list: VectorAbon): """добавляем список абонентов в NAS""" @abstractmethod - @check_input_type(AbonStruct) - def remove_user_range(self, users): + def remove_user_range(self, users: VectorAbon): """удаляем список абонентов""" @abstractmethod - @check_input_type(AbonStruct) - def add_user(self, user, *args): + def add_user(self, user: AbonStruct, *args): """добавляем абонента""" @abstractmethod - @check_input_type(AbonStruct) - def remove_user(self, user): + def remove_user(self, user: AbonStruct): """удаляем абонента""" @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 - @check_input_type(TariffStruct) - def add_tariff_range(self, tariff_list): + def add_tariff_range(self, tariff_list: VectorTariff): """ Пока не используется, зарезервировано. Добавляет список тарифов в NAS """ @abstractmethod - @check_input_type(TariffStruct) - def remove_tariff_range(self, tariff_list): + def remove_tariff_range(self, tariff_list: VectorTariff): """ Пока не используется, зарезервировано. Удаляем список тарифов по уникальным идентификаторам """ @abstractmethod - @check_input_type(TariffStruct) - def add_tariff(self, tariff): + def add_tariff(self, tariff: TariffStruct): """ Пока не используется, зарезервировано. - Добавляем тариф + Добавляет тариф """ @abstractmethod - @check_input_type(TariffStruct) - def update_tariff(self, tariff): + def update_tariff(self, tariff: TariffStruct): """ Пока не используется, зарезервировано. Чтоб обновить тариф надо изменить всё кроме его tid, по tid тариф будет найден """ @abstractmethod - @check_input_type(TariffStruct) - def remove_tariff(self, tid): + def remove_tariff(self, tid: int): """ :param tid: id тарифа в среде NAS сервера чтоб удалить по этому номеру Пока не используется, зарезервировано. """ @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 count: количество пингов diff --git a/mydefs.py b/mydefs.py index 49b6020..1ec4bdd 100644 --- a/mydefs.py +++ b/mydefs.py @@ -124,7 +124,7 @@ def only_admins(fn): 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)) return True if response == 0 else False diff --git a/tariff_app/base_intr.py b/tariff_app/base_intr.py index f03356b..cd2f8aa 100644 --- a/tariff_app/base_intr.py +++ b/tariff_app/base_intr.py @@ -1,20 +1,21 @@ -# -*- coding: utf-8 -*- from abc import ABCMeta, abstractmethod +from datetime import datetime +from typing import AnyStr, Optional, Union class TariffBase(metaclass=ABCMeta): @abstractmethod - def calc_amount(self): + def calc_amount(self) -> float: """Calculates total amount of payment""" raise NotImplementedError @abstractmethod - def calc_deadline(self): + def calc_deadline(self) -> datetime: """Calculate deadline date""" raise NotImplementedError @staticmethod - def description(): + def description() -> AnyStr: """ Usage in mydefs.MyChoicesAdapter for choices fields. :return: human readable description @@ -22,19 +23,19 @@ class TariffBase(metaclass=ABCMeta): raise NotImplementedError @staticmethod - def manage_access(abon): + def manage_access(abon) -> bool: """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() if act_tar: return True + return False class PeriodicPayCalcBase(metaclass=ABCMeta): - @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 :return: float: amount for the service @@ -42,7 +43,7 @@ class PeriodicPayCalcBase(metaclass=ABCMeta): raise NotImplementedError @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 last_time_payment: May be None if first pay @@ -51,7 +52,7 @@ class PeriodicPayCalcBase(metaclass=ABCMeta): raise NotImplementedError @staticmethod - def description(): + def description() -> AnyStr: """Return text description. Uses in mydefs.MyChoicesAdapter for CHOICES fields""" raise NotImplementedError diff --git a/tariff_app/custom_tariffs.py b/tariff_app/custom_tariffs.py index c3f6ff6..e9efd92 100644 --- a/tariff_app/custom_tariffs.py +++ b/tariff_app/custom_tariffs.py @@ -1,7 +1,9 @@ # -*- coding: utf-8 -*- from datetime import timedelta, datetime +from typing import AnyStr + 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 calendar import monthrange @@ -14,7 +16,7 @@ class TariffDefault(TariffBase): self.abon_tariff = abon_tariff # Базовый функционал считает стоимость пропорционально использованному времени - def calc_amount(self): + def calc_amount(self) -> float: # сейчас nw = timezone.now() @@ -35,7 +37,7 @@ class TariffDefault(TariffBase): return float(res) # Тут мы расчитываем конец действия услуги, завершение будет в конце месяца - def calc_deadline(self): + def calc_deadline(self) -> datetime: nw = timezone.now() last_day = monthrange(nw.year, nw.month)[1] last_month_date = datetime(year=nw.year, month=nw.month, day=last_day, @@ -43,7 +45,7 @@ class TariffDefault(TariffBase): return last_month_date @staticmethod - def description(): + def description() -> AnyStr: return _('Base calculate functionality') @@ -51,17 +53,17 @@ class TariffDp(TariffDefault): # в IS снимается вся стоимость тарифа вне зависимости от времени использования # просто возвращаем всю стоимость тарифа - def calc_amount(self): + def calc_amount(self) -> float: return float(self.abon_tariff.tariff.amount) @staticmethod - def description(): + def description() -> AnyStr: return 'IS' # Как в IS только не на время, а на 10 лет class TariffCp(TariffDp): - def calc_deadline(self): + def calc_deadline(self) -> datetime: # делаем время окончания услуги на 10 лет вперёд nw = timezone.now() 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 @staticmethod - def description(): + def description() -> AnyStr: return _('Private service') @@ -82,20 +84,20 @@ TARIFF_CHOICES = ( class PeriodicPayCalcDefault(PeriodicPayCalcBase): - def calc_amount(self, model_object): + def calc_amount(self, model_object) -> float: 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: решить какой будет расёт периодических платежей return datetime.now() + timedelta(days=30) @staticmethod - def description(): + def description() -> AnyStr: return _('Default periodic pay') 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 :return: float: amount for the service @@ -103,7 +105,7 @@ class PeriodicPayCalcCustom(PeriodicPayCalcDefault): return uniform(1, 10) @staticmethod - def description(): + def description() -> AnyStr: return _('Custom periodic pay') diff --git a/taskapp/context_proc.py b/taskapp/context_proc.py index 3bbdef8..857c5a3 100644 --- a/taskapp/context_proc.py +++ b/taskapp/context_proc.py @@ -1,5 +1,3 @@ -from django.contrib.auth.models import AnonymousUser - from taskapp.models import Task from accounts_app.models import UserProfile diff --git a/taskapp/forms.py b/taskapp/forms.py index 4afdd0d..a81443f 100644 --- a/taskapp/forms.py +++ b/taskapp/forms.py @@ -45,7 +45,7 @@ class TaskFrm(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.author = author comment.task = task