You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

649 lines
20 KiB

from datetime import datetime
from typing import Optional
from accounts_app.models import UserProfile, MyUserManager, BaseAccount
from bitfield import BitField
from django.conf import settings
from django.core import validators
from django.core.validators import RegexValidator
from django.db import models, connection, transaction
from django.db.models.signals import post_delete, pre_delete, post_init, \
pre_save
from django.dispatch import receiver
from django.shortcuts import resolve_url
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _, gettext
from djing.lib import LogicError
from group_app.models import Group
from gw_app.nas_managers import SubnetQueue, NasFailedResult, NasNetworkError
from ip_pool.models import NetworkModel
from tariff_app.models import Tariff, PeriodicPay
class AbonLog(models.Model):
abon = models.ForeignKey('Abon', on_delete=models.CASCADE)
amount = models.FloatField(default=0.0)
author = models.ForeignKey(UserProfile, on_delete=models.SET_NULL,
related_name='+', blank=True, null=True)
comment = models.CharField(max_length=128)
date = models.DateTimeField(auto_now_add=True)
class Meta:
db_table = 'abonent_log'
ordering = '-date',
def __str__(self):
return self.comment
class AbonTariff(models.Model):
tariff = models.ForeignKey(
Tariff,
on_delete=models.CASCADE,
related_name='linkto_tariff'
)
time_start = models.DateTimeField(null=True, blank=True, default=None)
deadline = models.DateTimeField(null=True, blank=True, default=None)
def calc_amount_service(self):
amount = self.tariff.amount
return round(amount, 2)
def __str__(self):
return "%s: %s" % (
self.deadline,
self.tariff.title
)
class Meta:
db_table = 'abonent_tariff'
permissions = (
('can_complete_service', _('finish service perm')),
)
verbose_name = _('Abon service')
verbose_name_plural = _('Abon services')
ordering = ('time_start',)
class AbonStreet(models.Model):
name = models.CharField(max_length=64)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
def __str__(self):
return self.name
class Meta:
db_table = 'abon_street'
verbose_name = _('Street')
verbose_name_plural = _('Streets')
ordering = 'name',
class AbonManager(MyUserManager):
def get_queryset(self):
return super(MyUserManager, self).get_queryset().filter(is_admin=False)
class Abon(BaseAccount):
current_tariff = models.OneToOneField(
AbonTariff,
null=True,
blank=True,
on_delete=models.SET_NULL,
default=None
)
group = models.ForeignKey(
Group,
on_delete=models.SET_NULL,
blank=True, null=True,
verbose_name=_('User group')
)
ballance = models.FloatField(default=0.0)
ip_address = models.GenericIPAddressField(
verbose_name=_('Ip address'),
null=True,
blank=True
)
description = models.TextField(
_('Comment'),
null=True,
blank=True
)
street = models.ForeignKey(
AbonStreet,
on_delete=models.SET_NULL,
null=True,
blank=True,
verbose_name=_('Street')
)
house = models.CharField(
_('House'),
max_length=12,
null=True,
blank=True
)
device = models.ForeignKey(
'devapp.Device',
null=True,
blank=True,
on_delete=models.SET_NULL
)
dev_port = models.ForeignKey(
'devapp.Port',
null=True,
blank=True,
on_delete=models.SET_NULL
)
is_dynamic_ip = models.BooleanField(
_('Is dynamic ip'),
default=False
)
nas = models.ForeignKey(
'gw_app.NASModel',
null=True,
blank=True,
on_delete=models.SET_NULL,
verbose_name=_('Network access server'),
default=None
)
autoconnect_service = models.BooleanField(
_('Automatically connect next service'),
default=False
)
last_connected_tariff = models.ForeignKey(
Tariff, verbose_name=_('Last connected service'),
on_delete=models.SET_NULL, null=True, blank=True, default=None
)
MARKER_FLAGS = (
('icon_donkey', _('Donkey')),
('icon_fire', _('Fire')),
('icon_ok', _('Ok')),
('icon_king', _('King')),
('icon_tv', _('TV')),
('icon_smile', _('Smile')),
('icon_dollar', _('Dollar')),
('icon_service', _('Service')),
('icon_mrk', _('Marker'))
)
markers = BitField(flags=MARKER_FLAGS, default=0)
def get_flag_icons(self):
"""
Return icon list of set flags from self.markers
:return: ['m-icon-donkey', 'm-icon-tv', ...]
"""
return tuple("m-%s" % name for name, state in self.markers if state)
def is_markers_empty(self):
return int(self.markers) == 0
def active_tariff(self):
return self.current_tariff
objects = AbonManager()
class Meta:
db_table = 'abonent'
permissions = (
('can_buy_tariff', _('Buy service perm')),
('can_add_ballance', _('fill account')),
('can_ping', _('Can ping'))
)
verbose_name = _('Abon')
verbose_name_plural = _('Abons')
ordering = ('fio',)
unique_together = ('ip_address', 'nas')
def add_ballance(self, current_user, amount, comment):
AbonLog.objects.create(
abon=self,
amount=amount,
author=current_user if isinstance(current_user,
UserProfile) else None,
comment=comment
)
self.ballance += amount
def pick_tariff(self, tariff, author, comment=None, deadline=None) -> None:
"""
Trying to buy a service if enough money.
:param tariff: instance of tariff_app.models.Tariff.
:param author: Instance of accounts_app.models.UserProfile.
Who connected this service. May be None if author is a system.
:param comment: Optional text for logging this pay.
:param deadline: Instance of datetime.datetime. Date when service is
expired.
:return: Nothing
"""
if not isinstance(tariff, Tariff):
raise TypeError
amount = round(tariff.amount, 2)
if tariff.is_admin and author is not None:
if not author.is_staff:
raise LogicError(
_('User that is no staff can not buy admin services')
)
if self.current_tariff is not None:
if self.current_tariff.tariff == tariff:
# if service already connected
raise LogicError(_('That service already activated'))
else:
# if service is present then speak about it
raise LogicError(_('Service already activated'))
# if not enough money
if self.ballance < amount:
raise LogicError(_('not enough money'))
with transaction.atomic():
new_abtar = AbonTariff.objects.create(
deadline=deadline, tariff=tariff
)
self.current_tariff = new_abtar
if self.last_connected_tariff != tariff:
self.last_connected_tariff = tariff
# charge for the service
self.ballance -= amount
self.save(update_fields=(
'ballance',
'current_tariff',
'last_connected_tariff'
))
# make log about it
AbonLog.objects.create(
abon=self, amount=-tariff.amount,
author=author,
comment=comment or _('Buy service default log')
)
def attach_ip_addr(self, ip, strict=False):
"""
Attach ip address to account
:param ip: Instance of str or ip_address
:param strict: If strict is True then ip not replaced quietly
:return: None
"""
if strict and self.ip_address:
raise LogicError('Ip address already exists')
self.ip_address = ip
self.save(update_fields=('ip_address',))
def free_ip_addr(self) -> bool:
if self.ip_address:
self.ip_address = None
self.save(update_fields=('ip_address',))
return True
return False
# is subscriber have access to service,
# view in tariff_app.custom_tariffs.<TariffBase>.manage_access()
def is_access(self) -> bool:
if not self.is_active:
return False
abon_tariff = self.active_tariff()
if abon_tariff is None:
return False
trf = abon_tariff.tariff
ct = trf.get_calc_type()(abon_tariff)
return ct.manage_access(self)
# make subscriber from agent structure
def build_agent_struct(self):
if not self.ip_address:
return
abon_tariff = self.active_tariff()
if abon_tariff:
abon_tariff = abon_tariff.tariff
return SubnetQueue(
name="uid%d" % self.pk,
network=self.ip_address,
max_limit=(abon_tariff.speedIn, abon_tariff.speedOut),
is_access=self.is_access()
)
def nas_sync_self(self) -> Optional[Exception]:
"""
Synchronize user with gateway
:return:
"""
if self.nas is None:
raise LogicError(_('gateway required'))
try:
agent_abon = self.build_agent_struct()
if agent_abon is not None:
mngr = self.nas.get_nas_manager()
mngr.update_user(agent_abon)
except (NasFailedResult, NasNetworkError, ConnectionResetError) as e:
print('ERROR:', e)
return e
except LogicError:
pass
def nas_add_self(self):
"""
Will add this user to network access server
:return:
"""
if self.nas is None:
raise LogicError(_('gateway required'))
try:
agent_abon = self.build_agent_struct()
if agent_abon is not None:
mngr = self.nas.get_nas_manager()
mngr.add_user(agent_abon)
except (NasFailedResult, NasNetworkError, ConnectionResetError) as e:
print('ERROR:', e)
return e
except LogicError:
pass
def nas_remove_self(self):
"""
Will remove this user to network access server
:return:
"""
if self.nas is None:
raise LogicError(_('gateway required'))
try:
agent_abon = self.build_agent_struct()
if agent_abon is not None:
mngr = self.nas.get_nas_manager()
mngr.remove_user(agent_abon)
except (NasFailedResult, NasNetworkError, ConnectionResetError) as e:
print('ERROR:', e)
return e
except LogicError:
pass
def get_absolute_url(self):
return resolve_url('abonapp:abon_home', self.group.id, self.username)
def enable_service(self, tariff: Tariff, deadline=None, time_start=None):
"""
Makes a services for current user, without money
:param tariff: Instance of service
:param deadline: Time when service is expired
:param time_start: Time when service has started
:return: None
"""
if deadline is None:
deadline = tariff.calc_deadline()
if time_start is None:
time_start = datetime.now()
new_abtar = AbonTariff.objects.create(
deadline=deadline, tariff=tariff,
time_start=time_start
)
self.current_tariff = new_abtar
self.last_connected_tariff = tariff
self.save(update_fields=('current_tariff', 'last_connected_tariff'))
class PassportInfo(models.Model):
series = models.CharField(
_('Pasport serial'),
max_length=4,
validators=(validators.integer_validator,)
)
number = models.CharField(
_('Pasport number'),
max_length=6,
validators=(validators.integer_validator,)
)
distributor = models.CharField(
_('Distributor'),
max_length=64
)
date_of_acceptance = models.DateField(_('Date of acceptance'))
abon = models.OneToOneField(
Abon,
on_delete=models.CASCADE,
blank=True,
null=True
)
class Meta:
db_table = 'passport_info'
verbose_name = _('Passport Info')
verbose_name_plural = _('Passport Info')
ordering = ('series',)
def __str__(self):
return "%s %s" % (self.series, self.number)
class InvoiceForPayment(models.Model):
abon = models.ForeignKey(Abon, on_delete=models.CASCADE)
status = models.BooleanField(default=False)
amount = models.FloatField(default=0.0)
comment = models.CharField(max_length=128)
date_create = models.DateTimeField(auto_now_add=True)
date_pay = models.DateTimeField(blank=True, null=True)
author = models.ForeignKey(
UserProfile,
related_name='+',
on_delete=models.SET_NULL,
blank=True,
null=True
)
def __str__(self):
return "%s -> %.2f" % (self.abon.username, self.amount)
def set_ok(self):
self.status = True
self.date_pay = timezone.now()
def get_prev_invoice(self):
return self.objects.order
class Meta:
ordering = ('date_create',)
db_table = 'abonent_inv_pay'
verbose_name = _('Debt')
verbose_name_plural = _('Debts')
class AllTimePayLogManager(models.Manager):
@staticmethod
def by_days():
cur = connection.cursor()
cur.execute(
'SELECT SUM(summ) AS alsum, '
'DATE_FORMAT(date_add, "%Y-%m-%d") AS pay_date '
'FROM all_time_pay_log '
'GROUP BY DATE_FORMAT(date_add, "%Y-%m-%d")'
)
while True:
r = cur.fetchone()
if r is None:
break
summ, dat = r
yield {
'summ': summ,
'pay_date': datetime.strptime(dat, '%Y-%m-%d')
}
# Log for pay system "AllTime"
class AllTimePayLog(models.Model):
abon = models.ForeignKey(
Abon,
on_delete=models.SET_DEFAULT,
blank=True,
null=True,
default=None
)
pay_id = models.CharField(
max_length=36,
unique=True,
primary_key=True
)
date_add = models.DateTimeField(auto_now_add=True)
summ = models.FloatField(default=0.0)
trade_point = models.CharField(
_('Trade point'),
max_length=20,
default=None,
null=True,
blank=True
)
receipt_num = models.BigIntegerField(_('Receipt number'), default=0)
objects = AllTimePayLogManager()
def __str__(self):
return self.pay_id
class Meta:
db_table = 'all_time_pay_log'
ordering = ('-date_add',)
# log for all terminals
class AllPayLog(models.Model):
pay_id = models.CharField(max_length=64, primary_key=True)
date_action = models.DateTimeField(auto_now_add=True)
summ = models.FloatField(default=0.0)
pay_system_name = models.CharField(max_length=16)
def __str__(self):
return self.pay_system_name
class Meta:
db_table = 'all_pay_log'
ordering = ('-date_action',)
class AbonRawPassword(models.Model):
account = models.OneToOneField(Abon, models.CASCADE, primary_key=True)
passw_text = models.CharField(max_length=64)
def __str__(self):
return "%s - %s" % (self.account, self.passw_text)
class Meta:
db_table = 'abon_raw_password'
class AdditionalTelephone(models.Model):
abon = models.ForeignKey(
Abon,
on_delete=models.CASCADE,
related_name='additional_telephones'
)
telephone = models.CharField(
max_length=16,
verbose_name=_('Telephone'),
# unique=True,
validators=(RegexValidator(
getattr(settings, 'TELEPHONE_REGEXP', r'^(\+[7,8,9,3]\d{10,11})?$')
),)
)
owner_name = models.CharField(max_length=127)
def __str__(self):
return "%s - (%s)" % (self.owner_name, self.telephone)
class Meta:
db_table = 'additional_telephones'
ordering = ('owner_name',)
verbose_name = _('Additional telephone')
verbose_name_plural = _('Additional telephones')
class PeriodicPayForId(models.Model):
periodic_pay = models.ForeignKey(
PeriodicPay,
on_delete=models.CASCADE,
verbose_name=_('Periodic pay')
)
last_pay = models.DateTimeField(_('Last pay time'), blank=True, null=True)
next_pay = models.DateTimeField(_('Next time to pay'))
account = models.ForeignKey(
Abon,
on_delete=models.CASCADE,
verbose_name=_('Account')
)
def payment_for_service(self, author: UserProfile = None, now=None):
"""
Charge for the service and leave a log about it
:param now: Current date, if now is None than it calculates in here
:param author: instance of UserProfile
"""
if now is None:
now = timezone.now()
if self.next_pay < now:
pp = self.periodic_pay
amount = pp.calc_amount()
next_pay_date = pp.get_next_time_to_pay(self.last_pay)
abon = self.account
with transaction.atomic():
abon.add_ballance(author, -amount, comment=gettext(
'Charge for "%(service)s"') % {
'service': self.periodic_pay
})
abon.save(update_fields=('ballance',))
self.last_pay = now
self.next_pay = next_pay_date
self.save(update_fields=('last_pay', 'next_pay'))
def __str__(self):
return "%s %s" % (self.periodic_pay, self.next_pay)
class Meta:
db_table = 'periodic_pay_for_id'
ordering = ('last_pay',)
@receiver(post_delete, sender=Abon)
def abon_del_signal(sender, **kwargs):
abon = kwargs.get("instance")
if abon is None:
raise ValueError('Instance does not passed to a signal')
try:
abon.nas_remove_self()
except (NasFailedResult, NasNetworkError, LogicError):
return True
@receiver(post_init, sender=AbonTariff)
def abon_tariff_post_init(sender, **kwargs):
abon_tariff = kwargs["instance"]
if getattr(abon_tariff, 'time_start') is None:
abon_tariff.time_start = timezone.now()
if getattr(abon_tariff, 'deadline') is None:
calc_obj = abon_tariff.tariff.get_calc_type()(abon_tariff)
abon_tariff.deadline = calc_obj.calc_deadline()
@receiver(pre_save, sender=AbonTariff)
def abon_tariff_pre_save(sender, **kwargs):
abon_tariff = kwargs["instance"]
if getattr(abon_tariff, 'deadline') is None:
calc_obj = abon_tariff.tariff.get_calc_type()(abon_tariff)
abon_tariff.deadline = calc_obj.calc_deadline()
@receiver(pre_delete, sender=AbonTariff)
def abontariff_pre_delete(sender, **kwargs):
abon_tariff = kwargs.get("instance")
if abon_tariff is None:
raise ValueError('Instance does not passed to a signal')
try:
abon = Abon.objects.get(current_tariff=abon_tariff)
abon.nas_remove_self()
except (NasFailedResult, NasNetworkError, LogicError):
return True
except Abon.DoesNotExist:
print('Error: abontariff_pre_delete - user not found')