Browse Source

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

devel
Dmitry Novikov 9 years ago
parent
commit
5ee1a3972f
  1. 2
      abonapp/forms.py
  2. 23
      abonapp/models.py
  3. 4
      abonapp/pay_systems.py
  4. 12
      accounts_app/models.py
  5. 4
      agent/core.py
  6. 51
      agent/mod_mikrotik.py
  7. 18
      agent/structs.py
  8. 3
      chatbot/locale/ru/LC_MESSAGES/django.po
  9. 4
      chatbot/models.py
  10. 11
      chatbot/telebot.py
  11. 2
      cron.py
  12. 9
      devapp/dev_types.py
  13. 3
      devapp/tests.py
  14. 4
      devapp/views.py
  15. 2
      djing/settings_example.py
  16. 0
      docs/ats.md
  17. 3
      docs/dev.md
  18. BIN
      docs/img/login.png
  19. 294
      docs/install.md
  20. 9
      mydefs.py
  21. 7
      photo_app/models.py
  22. 7
      requirements.txt
  23. 3
      setup.py
  24. 3
      tariff_app/models.py
  25. 2
      templates/toolbar_page.html

2
abonapp/forms.py

@ -70,7 +70,7 @@ class AbonForm(forms.ModelForm):
def save(self, commit=True):
raw_password = self.cleaned_data['password']
acc = super().save(commit=False)
acc = super(AbonForm, self).save(commit=False)
acc.password = make_password(raw_password)
if commit:
acc.save()

23
abonapp/models.py

@ -164,7 +164,8 @@ class Abon(UserProfile):
# покупаем тариф
def pick_tariff(self, tariff, author, comment=None, deadline=None):
assert isinstance(tariff, Tariff)
if not isinstance(tariff, Tariff):
raise TypeError
amount = round(tariff.amount, 2)
@ -246,7 +247,7 @@ class PassportInfo(models.Model):
date_of_acceptance = models.DateField()
abon = models.OneToOneField(Abon, on_delete=models.SET_NULL, blank=True, null=True)
def __unicode__(self):
def __str__(self):
return "%s %s" % (self.series, self.number)
@ -330,7 +331,7 @@ def abon_post_save(sender, instance, **kwargs):
# обновляем абонента на NAS
tm.update_user(agent_abon, ip_timeout=timeout)
except (NasFailedResult, NasNetworkError) as e:
except (NasFailedResult, NasNetworkError, ConnectionResetError) as e:
print('ERROR:', e)
return True
@ -356,6 +357,22 @@ def abon_tariff_post_init(sender, instance, **kwargs):
instance.deadline = calc_obj.calc_deadline()
def abontariff_pre_delete(sender, instance, **kwargs):
try:
abon = Abon.objects.get(current_tariff=instance)
ab = abon.build_agent_struct()
if ab is None:
return True
tm = Transmitter()
tm.remove_user(ab)
except Abon.DoesNotExist:
print('ERROR: Abon.DoesNotExist')
except (NasFailedResult, NasNetworkError, ConnectionResetError) as e:
print('NetErr:', e)
return True
models.signals.post_save.connect(abon_post_save, sender=Abon)
models.signals.post_delete.connect(abon_del_signal, sender=Abon)
models.signals.post_init.connect(abon_tariff_post_init, sender=AbonTariff)
models.signals.pre_delete.connect(abontariff_pre_delete, sender=AbonTariff)

4
abonapp/pay_systems.py

@ -3,9 +3,11 @@ from django.utils import timezone
from mydefs import safe_int, safe_float
from .models import Abon, AllTimePayLog
from django.db import DatabaseError
from django.conf import settings
from djing.settings import pay_SECRET as SECRET, pay_SERV_ID as SERV_ID
SECRET = getattr(settings, 'pay_SECRET')
SERV_ID = getattr(settings, 'pay_SERV_ID')
#?ACT=1&PAY_ACCOUNT=960849&SERVICE_ID=y832r92y8f9e&PAY_ID=3561234&TRADE_POINT=377&SIGN=32e533a72389fe4e93746509f9d672f8

12
accounts_app/models.py

@ -3,11 +3,14 @@ from django.db import models
from django.contrib.auth.models import BaseUserManager, AbstractBaseUser, PermissionsMixin
from django.core.validators import RegexValidator
from django.utils.translation import ugettext as _
from djing.settings import DEFAULT_PICTURE
from django.conf import settings
from photo_app.models import Photo
DEFAULT_PICTURE = getattr(settings, 'DEFAULT_PICTURE', '/static/images/default-avatar.png')
TELEPHONE_REGEXP = getattr(settings, 'TELEPHONE_REGEXP', r'^\+[7,8,9,3]\d{10,11}$')
class MyUserManager(BaseUserManager):
def create_user(self, telephone, username, password=None):
"""
@ -51,7 +54,7 @@ class UserProfile(AbstractBaseUser, PermissionsMixin):
max_length=16,
verbose_name=_('Telephone'),
#unique=True,
validators=[RegexValidator('^\+[7,8,9,3]\d{10,11}$')]
validators=[RegexValidator(TELEPHONE_REGEXP)]
)
avatar = models.ForeignKey(Photo, null=True, blank=True, on_delete=models.SET_NULL)
email = models.EmailField(default='admin@example.ru')
@ -65,13 +68,12 @@ class UserProfile(AbstractBaseUser, PermissionsMixin):
def get_short_name(self):
return self.username or self.telephone
# Use UserManager to get the create_user method, etc.
objects = MyUserManager()
@property
def is_staff(self):
"Is the user a member of staff?"
" Is the user a member of staff?"
# Simplest possible answer: All admins are staff
return self.is_admin

4
agent/core.py

@ -19,8 +19,6 @@ class NasNetworkError(Exception):
def check_input_type(*types):
def real_check(fn):
def wrapped(self, *args):
if len(types) != len(args):
raise AttributeError("length of @types must be equivalent for length of @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)))
@ -97,7 +95,7 @@ class BaseTransmitter(metaclass=ABCMeta):
"""
@abstractmethod
@check_input_type(str)
@check_input_type(str, int)
def ping(self, host, count=10):
"""
:param host: ip адрес в текстовом виде, например '192.168.0.1'

51
agent/mod_mikrotik.py

@ -6,12 +6,12 @@ from hashlib import md5
from .core import BaseTransmitter, NasFailedResult, NasNetworkError
from mydefs import ping
from .structs import TariffStruct, AbonStruct, IpStruct
from . import settings
from djing.settings import DEBUG
from . import settings as local_settings
from django.conf import settings
import re
#DEBUG=True
DEBUG = getattr(settings, 'DEBUG', False)
LIST_USERS_ALLOWED = 'DjingUsersAllowed'
LIST_USERS_BLOCKED = 'DjingUsersBlocked'
@ -144,15 +144,17 @@ class ApiRos:
class TransmitterManager(BaseTransmitter, metaclass=ABCMeta):
def __init__(self, login=None, password=None, ip=None, port=None):
ip = ip or settings.NAS_IP
ip = ip or getattr(local_settings, 'NAS_IP')
if ip is None:
raise NasNetworkError('Не передан ip адрес NAS')
if not ping(ip):
raise NasNetworkError('NAS %s не пингуется' % ip)
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((ip, port or settings.NAS_PORT))
s.connect((ip, port or getattr(local_settings, 'NAS_PORT', 8728)))
self.s = s
self.ar = ApiRos(s)
self.ar.login(login or settings.NAS_LOGIN, password or 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)
@ -161,7 +163,8 @@ class TransmitterManager(BaseTransmitter, metaclass=ABCMeta):
self.s.close()
def _exec_cmd(self, cmd):
assert isinstance(cmd, list)
if not isinstance(cmd, list):
raise TypeError
result_iter = self.ar.talk_iter(cmd)
res = []
for rt in result_iter:
@ -171,7 +174,8 @@ class TransmitterManager(BaseTransmitter, metaclass=ABCMeta):
return res
def _exec_cmd_iter(self, cmd):
assert isinstance(cmd, list)
if not isinstance(cmd, list):
raise TypeError
result_iter = self.ar.talk_iter(cmd)
for rt in result_iter:
if len(rt) < 2:
@ -223,7 +227,8 @@ class QueueManager(TransmitterManager, metaclass=ABCMeta):
return self._build_shape_obj(ret[0])
def add(self, user):
assert isinstance(user, AbonStruct)
if not isinstance(user, AbonStruct):
raise TypeError
if user.tariff is None or not isinstance(user.tariff, TariffStruct):
return
return self._exec_cmd(['/queue/simple/add',
@ -236,7 +241,8 @@ class QueueManager(TransmitterManager, metaclass=ABCMeta):
])
def remove(self, user):
assert isinstance(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', '')])
@ -246,7 +252,8 @@ class QueueManager(TransmitterManager, metaclass=ABCMeta):
return self._exec_cmd(['/queue/simple/remove', '=numbers=' + ','.join(q_ids)])
def update(self, user):
assert isinstance(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)
@ -282,7 +289,8 @@ class QueueManager(TransmitterManager, metaclass=ABCMeta):
yield int(queue[1]['=.id'].replace('*', ''), base=16)
def disable(self, user):
assert isinstance(user, AbonStruct)
if not isinstance(user, AbonStruct):
raise TypeError
q = self.find('uid%d' % user.uid)
if q is None:
self.add(user)
@ -291,7 +299,8 @@ class QueueManager(TransmitterManager, metaclass=ABCMeta):
return self._exec_cmd(['/queue/simple/disable', '=.id=*' + getattr(q, 'queue_id', '')])
def enable(self, user):
assert isinstance(user, AbonStruct)
if not isinstance(user, AbonStruct):
raise TypeError
q = self.find('uid%d' % user.uid)
if q is None:
self.add(user)
@ -309,7 +318,8 @@ class IpAddressListObj(IpStruct):
class IpAddressListManager(TransmitterManager, metaclass=ABCMeta):
def add(self, list_name, ip, timeout=None):
assert isinstance(ip, IpStruct)
if not isinstance(ip, IpStruct):
raise TypeError
commands = [
'/ip/firewall/address-list/add',
'=list=%s' % list_name,
@ -342,7 +352,8 @@ class IpAddressListManager(TransmitterManager, metaclass=ABCMeta):
])
def find(self, ip, list_name):
assert isinstance(ip, IpStruct)
if not isinstance(ip, IpStruct):
raise TypeError
return self._exec_cmd([
'/ip/firewall/address-list/print', 'where',
'?list=%s' % list_name,
@ -392,8 +403,9 @@ class MikrotikTransmitter(QueueManager, IpAddressListManager):
IpAddressListManager.remove(self, ip_list_entity[0]['=.id'])
def add_user(self, user, ip_timeout=None):
super(MikrotikTransmitter, self).add_user(user)
assert isinstance(user.ip, IpStruct)
super(MikrotikTransmitter, self).add_user(user, ip_timeout)
if not isinstance(user.ip, IpStruct):
raise TypeError
if user.tariff is None or not isinstance(user.tariff, TariffStruct):
return
QueueManager.add(self, user)
@ -412,8 +424,9 @@ class MikrotikTransmitter(QueueManager, IpAddressListManager):
# обновляем основную инфу абонента
def update_user(self, user, ip_timeout=None):
super(MikrotikTransmitter, self).update_user(user)
assert isinstance(user.ip, IpStruct)
super(MikrotikTransmitter, self).update_user(user, ip_timeout)
if not isinstance(user.ip, IpStruct):
raise TypeError
# ищем ip абонента в списке ip
find_res = IpAddressListManager.find(self, user.ip, LIST_USERS_ALLOWED)

18
agent/structs.py

@ -38,7 +38,8 @@ class IpStruct(BaseStruct):
return self.__ip
def __eq__(self, other):
assert isinstance(other, IpStruct)
if not isinstance(other, IpStruct):
raise TypeError('Instance must be IpStruct')
return self.__ip == other.__ip
def __int__(self):
@ -100,8 +101,10 @@ class AbonStruct(BaseStruct):
def serialize(self):
if self.tariff is None:
return
assert isinstance(self.tariff, TariffStruct)
assert isinstance(self.ip, IpStruct)
if not isinstance(self.tariff, TariffStruct):
raise TypeError('Instance must be TariffStruct')
if not isinstance(self.ip, IpStruct):
raise TypeError('Instance must be IpStruct')
dt = pack("!LII?", self.uid, int(self.ip), self.tariff.tid, self.is_active)
return dt
@ -110,13 +113,15 @@ class AbonStruct(BaseStruct):
self.uid = dt[0]
self.ip = IpStruct(dt[1])
if tariff is not None:
assert isinstance(tariff, TariffStruct)
if not isinstance(tariff, TariffStruct):
raise TypeError
self.tariff = tariff
self.is_active = dt['3']
return self
def __eq__(self, other):
assert isinstance(other, AbonStruct)
if not isinstance(other, AbonStruct):
raise TypeError
r = self.uid == other.uid and self.ip == other.ip
r = r and self.tariff == other.tariff
return r
@ -147,5 +152,6 @@ class ShapeItem(BaseStruct):
return self
def __eq__(self, other):
assert isinstance(other, ShapeItem)
if not isinstance(other, ShapeItem):
raise TypeError
return self.sid == other.sid and self.abon == other.abon

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

@ -55,3 +55,6 @@ msgstr "Давай пинганём, напиши ip. Нужно будет по
msgid "Yes, it's nice to meet% s, I will notify you about events in billing. Successful work;)"
msgstr "Да, приятно познакомиться %s, я буду оповещать тебя о событиях в биллинге. Удачной работы ;)"
msgid "Telegram bot token not found"
msgstr "Токен для бота Telegram не найден"

4
chatbot/models.py

@ -1,5 +1,7 @@
from django.db import models
from djing.settings import AUTH_USER_MODEL
from django.conf import settings
AUTH_USER_MODEL = getattr(settings, 'AUTH_USER_MODEL')
class ChatException(Exception):

11
chatbot/telebot.py

@ -31,7 +31,8 @@ class DjingTelebot(helper.ChatHandler):
# задаём вопрос пользователю, и ожидаем ответ в fn
def _question(self, text, fn):
assert isinstance(fn, collections.Callable)
if not isinstance(fn, collections.Callable):
raise TypeError
self._dialog_fn = fn
if text is not None:
self._sent_reply(text)
@ -75,7 +76,8 @@ class DjingTelebot(helper.ChatHandler):
if text in list(self.cmds.keys()):
self.cmds[text]()
elif self._dialog_fn is not None:
assert callable(self._dialog_fn)
if not callable(self._dialog_fn):
raise TypeError
self._dialog_fn(text)
self._dialog_fn = None
else:
@ -92,7 +94,8 @@ class DjingTelebot(helper.ChatHandler):
try:
TelegramBot.objects.get(user=profile)
except TelegramBot.DoesNotExist:
assert self._chat_id != 0
if self._chat_id == 0:
raise ChatException('telebot.py. def question_name: Chat id is empty')
TelegramBot.objects.create(
user=profile,
chat_id=self._chat_id
@ -130,6 +133,8 @@ class DjingTelebot(helper.ChatHandler):
# Просто отправляем текст оповещения указанному админу
def send_notify(msg_text, account):
try:
if token is None:
raise ChatException(_('Telegram bot token not found'))
tb = TelegramBot.objects.get(user=account)
tbot = Bot(token)
tbot.sendMessage(tb.chat_id, msg_text)

2
cron.py

@ -13,7 +13,7 @@ def main():
users = Abon.objects.all()
for user in users:
try:
# бдим за услугами абонента: просроченные отключить, заказанные подключить
# бдим за услугами абонента
user.bill_service(user)
# если нет ip то и нет смысла лезть в NAS

9
devapp/dev_types.py

@ -9,7 +9,8 @@ class DLinkPort(BasePort):
def __init__(self, num, name, status, mac, speed, snmpWorker):
BasePort.__init__(self, num, name, status, mac, speed)
assert issubclass(snmpWorker.__class__ , SNMPBaseWorker)
if not issubclass(snmpWorker.__class__ , SNMPBaseWorker):
raise TypeError
self.snmp_worker = snmpWorker
# выключаем этот порт
@ -79,7 +80,8 @@ class DLinkDevice(DevBase, SNMPBaseWorker):
class ONUdev(BasePort):
def __init__(self, num, name, status, mac, speed, signal, snmpWorker):
super(ONUdev, self).__init__(num, name, status, mac, speed)
assert issubclass(snmpWorker.__class__, SNMPBaseWorker)
if not issubclass(snmpWorker.__class__, SNMPBaseWorker):
raise TypeError
self.snmp_worker = snmpWorker
self.signal = signal
@ -181,7 +183,8 @@ class EltexPort(BasePort):
def __init__(self, snmpWorker, *args, **kwargs):
BasePort.__init__(self, *args, **kwargs)
assert issubclass(snmpWorker.__class__, SNMPBaseWorker)
if not issubclass(snmpWorker.__class__, SNMPBaseWorker):
raise TypeError
self.snmp_worker = snmpWorker
# выключаем этот порт

3
devapp/tests.py

@ -14,7 +14,8 @@ class DevTest(TestCase):
ports = dev.get_ports()
print('gports')
for port in ports:
assert issubclass(port.__class__, dev_types.BasePort)
if not issubclass(port.__class__, dev_types.BasePort):
raise TypeError
print(('\tPort:', port.nm, port.st, port.mac(), port.sp))
# Disable 2 port
print((ports[1].disable()))

4
devapp/views.py

@ -14,7 +14,7 @@ from .models import Device, Port, DeviceDBException
from mydefs import pag_mn, res_success, res_error, only_admins, ping, order_helper
from .forms import DeviceForm, PortForm
from abonapp.models import AbonGroup, Abon
from djing.settings import DEFAULT_SNMP_PASSWORD
from django.conf import settings
@login_required
@ -103,7 +103,7 @@ def dev(request, grp, devid=0):
'mac_addr': request.GET.get('mac'),
'comment': request.GET.get('c'),
'ip_address': request.GET.get('ip'),
'man_passw': DEFAULT_SNMP_PASSWORD
'man_passw': getattr(settings, 'DEFAULT_SNMP_PASSWORD', '')
})
else:
frm = DeviceForm(instance=devinst)

2
djing/settings_example.py

@ -168,3 +168,5 @@ DHCP_TIMEOUT = 14400
DEFAULT_SNMP_PASSWORD = 'public'
TELEGRAM_BOT_TOKEN = 'bot token'
TELEPHONE_REGEXP = r'^\+[7,8,9,3]\d{10,11}$'

0
docs/ats.md

3
docs/dev.md

@ -12,7 +12,8 @@ class EltexPort(BasePort):
def __init__(self, snmpWorker, *args, **kwargs):
BasePort.__init__(self, *args, **kwargs)
assert issubclass(snmpWorker.__class__, SNMPBaseWorker)
if not issubclass(snmpWorker.__class__, SNMPBaseWorker):
raise TypeError
self.snmp_worker = snmpWorker
# выключаем этот порт

BIN
docs/img/login.png

After

Width: 1239  |  Height: 809  |  Size: 14 KiB

294
docs/install.md

@ -2,7 +2,7 @@
Работа предполагается на python3.
Я предпочитаю запускать wsgi сервер на связке uWSGI + Nginx, так что ставить будем соответствующие пакеты.
#####На Fedora25 нужные пакеты можно установить так:
##### На Fedora25 нужные пакеты можно установить так:
Для начала подготовим систему, очистим и обновим пакеты. Процесс обновления долгий, так что можно пойти заварить себе чай :)
```
@ -12,13 +12,18 @@
Затем установим зависимости
```
# dnf -y install python3 python3-devel python3-pip python3-pillow mariadb uwsgi nginx redis net-snmp net-snmp-libs net-snmp-utils net-snmp-devel net-snmp-python git redhat-rpm-config
# dnf -y install python3 python3-devel python3-pip python3-pillow mariadb mariadb-devel uwsgi nginx uwsgi-plugin-python3 redis net-snmp net-snmp-libs net-snmp-utils net-snmp-devel net-snmp-python git redhat-rpm-config
```
Лучше чтоб версия python по умолчанию была третья:
```
# ln -sf python3 /usr/bin/python
```
Условимся что путь к папке с проектом находится по адресу: */var/www/djing*.
Дальше создадим каталок для web, затем обновляем pip и ставим проект через pip:
```
# mkdir /vaw/www
# mkdir /var/www
# cd /var/www
# pip3 install --upgrade pip
# git clone https://github.com/nerosketch/djing.git
@ -27,91 +32,254 @@
Скопируем конфиги из примеров в реальные:
```
cd /var/www/djing
cp djing/settings_example.py djing/settings.py
cp agent/settings.py.example agent/settings.py
$ cd /var/www/djing
# cp djing/settings_example.py djing/settings.py
# cp agent/settings.py.example agent/settings.py
```
Затем отредактируйте конфиги для своих нужд.
Для удобства я создаю пользователя и группу http:http, и всё что связано с web-сервером запускаю от имени http.
```
groupadd -r http
useradd -l -M -r -d /dev/null -g http -s /sbin/nologin http
chown -R http:http /var/www
chown -R http:http /etc/nginx
chown -R http:http /etc/uwsgi.*
# groupadd -r http
# useradd -l -M -r -d /dev/null -g http -s /sbin/nologin http
# chown -R http:http /var/www
# chown -R http:http /etc/nginx
# chown -R http:http /etc/uwsgi.*
# chown -R http:http /run/uwsgi/
```
### Настройка WEB Сервера
Конфиг Nginx на моём рабочем сервере выглядит так:
```nginx
user http;
worker_processes auto;
pid /run/nginx.pid;
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;
}
events {
worker_connections 1024;
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
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:
> /etc/uwsgi.ini
```ini
[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
```
Зададим конфиг для *uwsgi vassal*:
> /etc/uwsgi.d/djing.ini
```ini
[uwsgi]
chdir=/var/www/djing/
module=djing.wsgi
master=True
processes=8
;socket=/run/uwsgi/djing.sock
http-socket=:8000
chmod-socket=664
pidfile=/run/uwsgi/django-master.pid
vacuum=True
plugin=python3
```
Примените к созданному файлу пользователя http:
> \# chown http:http /etc/uwsgi.d/djing.ini
Перед пробой запуска отключим все ограничения фаервола:
> \# systemctl stop firewalld
Или даже отключить, если вы отложите настройку *firewalld* на потом:
> \# systemctl disable firewalld
Перед тем как попробовать запустить тестовый сервер скомпилируйте переводы:
> \$ ./manage.py compilemessages -l ru
Попробуем запустить *uwsgi* и djing без Nginx:
> \# uwsgi --gid http --uid http /etc/uwsgi.d/djing.ini
пробуем зайти в биллинг с браузера на <адрес сервера>:8000. Вам должен показаться диалог входа в систему:
![Login screenshot](./img/login.png)
Для того чтоб uwsgi применял к своим файлам пользователя http, надо подредактировать системный юнит uwsgi, у меня он имеет такой путь:
> /usr/lib/systemd/system/uwsgi.service
В нём надо чтоб chown менял пользователя на http, а не на uwsgi:
> ExecStartPre=/bin/chown -r http:http /run/uwsgi
Теперь, если всё прошло успешно, поменяйте в конфиге */etc/uwsgi.d/djing.ini* сокет с http на unix socket:
Раскомментируйте это:
> socket=/run/uwsgi/djing.sock
И закомментируйте эту строку:
> http-socket=:8000
Строка *http-socket=:8000* была для теста, чтоб посмотреть работает-ли uwsgi сам по себе.
[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
Теперь можно попробовать запустить *nginx* и *uwsgi*. Ставим в **djing/settings.py** опцию **DEBUG = False**, и пробуем запустить нужные юниты:
У меня конфиг лежит по адресу /etc/uwsgi.ini
> \# systemctl start uwsgi\
> \# systemctl start nginx
По умолчанию на fedora включено SELinux и вы не сможете зайти на сайт пока не настроите его. Для того, чтоб проверить всё
ли правильно мы настроили, отключите *SELinux* коммандой **setenforce 0* и попробуйте зайти. После успешного запуска вы
можете снова включить опцию и настроить её.
### Настраиваем биллинг
Все настройки биллинга находятся в файле *djing/settings.py*. Большинство опций вы можете найти в документации
[Django settings](https://docs.djangoproject.com/en/1.9/ref/settings).
Те опции, которые были добавлены мной в рамках проекта *djing*, описаны ниже в этом разделе документации по установке.
#### djing/settings.py
**USE_TZ** &mdash; Это опция *Django*, но если вы не работаете в разных часовых диапазонах то я не рекомендую включать
эту опцию чтоб небыло путаницы со временем. Это связано с тем что я ещё не тестировал поведение работы со временем при
включённой опции *USE_TZ*.
**ALLOWED_HOSTS** &mdash; Тоже опция *Django*, но важная для безопасности, укажите в списке возможные имена вашего сервера.
Подробнее в документации [Django settings](https://docs.djangoproject.com/en/1.9/ref/settings/#allowed-hosts).
**DEFAULT_PICTURE** &mdash; Это путь к изображению по умолчанию, оно используется когда нужное изображение не найдено.
**PAGINATION_ITEMS_PER_PAGE** &mdash; Количество выводимых элементов списка на странце с таблицей. Например, если поставить 30,
то на странице абонентов на одной странице будет выведено 30 строк абонентов.
**pay_SERV_ID** &mdash; Эта опция, так же как и **pay_SECRET** опции для платёжной системы *AllTime24*, если вы используете любую
другую платёжную систему то можете удалить эти опции.
**DIALING_MEDIA** &mdash; Путь, где биллинг сможет найти файлы записей asterisk чтоб вывести статистику звонков.
Подробнее читайте в описании работы с [АТС](./ats.ms).
**DHCP_TIMEOUT** &mdash; Время аренды настроек DHCP в секундах.
**DEFAULT_SNMP_PASSWORD** &mdash; Пароль snmp по умолчанию для устройств, чтоб при создании устройства он был заполнен в нужном поле.
Если нет такого пароля то оставьте пустым или None.
**TELEPHONE_REGEXP** &mdash; Регулярное выражение для валидации номера телефона.
#### Создание БД
Подразумевается что сервер баз данных у вас уже есть, или вы его можете установить сами.
В конфиге настроить БД можно по инструкции [Django databases](https://docs.djangoproject.com/en/1.9/ref/settings/#databases).
Убедитесть что вы в папке с проектом, комманда **pwd** должна выдать */var/www/djing*.
Чтоб создать бд, как описано в документации [Django admin \& migrate](https://docs.djangoproject.com/en/1.9/ref/django-admin/#migrate),
нужно запустить **./manage.py migrate** чтоб создать структуру БД. Вывод будет примерно таким:
```
$ ./manage.py migrate
Operations to perform:
Apply all migrations: mapapp, contenttypes, dialing_app, django_messages, taskapp, photo_app, accounts_app, devapp, statistics, tariff_app, admin, sessions, chatbot, auth, abonapp
Running migrations:
Rendering model states... DONE
Applying mapapp.0001_initial... OK
Applying devapp.0001_initial... OK
Applying devapp.0002_auto_20160909_1018... OK
Applying devapp.0003_device_map_dot... OK
Applying photo_app.0001_initial... OK
Applying contenttypes.0001_initial... OK
...
Applying taskapp.0012_auto_20170407_0124... OK
Applying taskapp.0013_auto_20170413_1944... OK
Applying taskapp.0014_auto_20170416_1029... OK
Applying taskapp.0015_auto_20170816_1109... OK
```
После этого вам стоит создать супер пользователя чтоб зайти в систему.
```
$ ./manage.py createsuperuser
```
В интерактивном режиме ответьте на вопросы.
```
$ ./manage.py createsuperuser
Username: username
Telephone: +12223334455
Password:
Password (again):
Superuser created successfully.
```
Обратите внимание на то что номер телефона это обязательное поле для заполнения.
Если у вас не выходит указать номер телефона, то проверте чтоб ваш телефон соответствовал регулярному выражению **^\+[7,8,9,3]\d{10,11}$**.
Если регулярное выражение вам не подхожит, то вы можете изенить его в настройках, см. опции в настройках выше.
После изменения настроек они не сразу вступят в силу, нужно перезагрузить код django, для этого перезапустите **uwsgi**:
> \# systemctl restart uwsgi
Теперь произведите тестовый запуск:
> \# ./manage.py runserver 192.168.0.100:8000
Если не подтягивается статика то проверте чтоб опция **DEBUG** в настройках была **True**.
При условии что адрес вашего сервера *192.168.0.100*, вы сможете открыть биллинг по адресу **http://192.168.0.100:8000/**.
Введите логин и пароль супер пользователя которого вы создали по инструкции выше.
Если вы успешно зашли то можно пробовать запускать биллинг в рабочую обстановку.
В настройках смените переменную **DEBUG** на **False** и перезапустите *uwsgi*.
### Настраиваем демоны
Если ваша система работает с поддержкой *systemd* то в каталоге *systemd_units* проекта вы найдёте юниты для systemd.
Если ваша система работает с поддержкой [**systemd**](https://www.freedesktop.org/wiki/Software/systemd/) то в каталоге *systemd_units* проекта вы найдёте юниты для systemd.
Скопируйте их в каталог юнитов systemd, у меня это путь */etc/systemd/system*.
__Настоятельно рекомендую заглянуть внутрь этих юнитов__. Проверте пути исполняемых файлов, права и прочее.
Для запуска сервиса **djing_rotate.service** вам нужно сначала настроить сбор статистики по [netflow](./netflow.md).
Перед включением юнита *djing_telebot.service* создайте Telegram бота и впишите в файл *djing/settings.py* в переменную *TELEGRAM_BOT_TOKEN* токен вашего бота.
С помощью этого бота вы будете получать различные сообщения из биллинга. Подробнее в инструкции к [модулю оповещений](./bot.md).
А теперь включим и запустим нужные демоны
```
```shell
# systemctl daemon-reload
# systemctl enable djing_queue.service
# systemctl start djing_queue.service
@ -120,5 +288,3 @@ __Настоятельно рекомендую заглянуть внутрь
# systemctl enable djing_telebot.service
# systemctl start djing_telebot.service
```
Перед включением юнита *djing_telebot.service* создайте Telegram бота и впишите в файл *djing/settings.py* в переменную *TELEGRAM_BOT_TOKEN* токен вашего бота.
С помощью этого бота вы будете получать различные сообщения из биллинга. Подробнее в инструкции к [модулю оповещений](./docs/bot.md).

9
mydefs.py

@ -10,9 +10,12 @@ from django.http import HttpResponse, Http404, HttpResponseRedirect
from django.shortcuts import redirect
from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage
from django.db import models
from djing.settings import PAGINATION_ITEMS_PER_PAGE, DEBUG
from django.conf import settings
PAGINATION_ITEMS_PER_PAGE = getattr(settings, 'PAGINATION_ITEMS_PER_PAGE', 10)
DEBUG = getattr(settings, 'DEBUG', False)
ip_addr_regex = r'^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'
@ -83,8 +86,8 @@ class MyGenericIPAddressField(models.GenericIPAddressField):
value = super(MyGenericIPAddressField, self).get_prep_value(value)
return ip2int(value)
def to_python(self, addr):
return addr
def to_python(self, value):
return value
def get_internal_type(self):
return 'PositiveIntegerField'

7
photo_app/models.py

@ -5,8 +5,9 @@ import hashlib
from django.db import models
from PIL import Image
from django.conf import settings
from djing.settings import BASE_DIR
BASE_DIR = getattr(settings, 'BASE_DIR', '.')
class Photo(models.Model):
@ -27,7 +28,7 @@ class Photo(models.Model):
def save(self, *args, **kwargs):
if not self.image:
return
super(Photo, self).save()
super(Photo, self).save(*args, **kwargs)
im = Image.open(self.image.path)
im.thumbnail((759, 759), Image.ANTIALIAS)
@ -42,7 +43,7 @@ class Photo(models.Model):
im.save(fname)
os.remove(self.image.path)
self.image = "%s.%s" % (hs, ext)
super(Photo, self).save()
super(Photo, self).save(*args, **kwargs)
# class Meta:
# unique_together = (('image',),)

7
requirements.txt

@ -1,11 +1,16 @@
Django==1.9
Pillow
telepot
# for mac address field
netaddr
# for testing required xmltodict
xmltodict
PyMySQL
# Django recommended mysql client
mysqlclient
easysnmp
rq
pid

3
setup.py

@ -1,3 +0,0 @@
#!/usr/bin/env python3
import os
import sys

3
tariff_app/models.py

@ -19,7 +19,8 @@ class Tariff(models.Model):
ob = [TC for TC in TARIFF_CHOICES if TC[0] == self.calc_type]
if len(ob) > 0:
res_type = ob[0][1]
assert issubclass(res_type, TariffBase)
if not issubclass(res_type, TariffBase):
raise TypeError
return res_type
def calc_deadline(self):

2
templates/toolbar_page.html

@ -1,3 +1,4 @@
{% if pag.count > 1 %}
{% load dpagination %}
<div class="row">
<div class="col-sm-4 col-sm-offset-4">
@ -22,3 +23,4 @@
</ul>
</div>
</div>
{% endif %}
Loading…
Cancel
Save