diff --git a/abonapp/migrations/0009_abontariff_death_line.py b/abonapp/migrations/0009_abontariff_death_line.py new file mode 100644 index 0000000..1152d33 --- /dev/null +++ b/abonapp/migrations/0009_abontariff_death_line.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9 on 2017-02-16 12:22 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('abonapp', '0008_auto_20170209_0002'), + ] + + operations = [ + migrations.AddField( + model_name='abontariff', + name='deadline', + field=models.DateTimeField(blank=True, default=None, null=True), + ), + ] diff --git a/abonapp/models.py b/abonapp/models.py index f5c7d54..eeb94a7 100644 --- a/abonapp/models.py +++ b/abonapp/models.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- from django.utils import timezone -from django.utils.datetime_safe import datetime from django.db import models from django.core.validators import DecimalValidator from agent import Transmitter, AbonStruct, TariffStruct, NasFailedResult @@ -10,14 +9,7 @@ from accounts_app.models import UserProfile class LogicError(Exception): - def __init__(self, value): - self.message = value - - def __str__(self): - return repr(self.message) - - def __str__(self): - return repr(self.message) + pass class AbonGroup(models.Model): @@ -56,6 +48,9 @@ class AbonTariff(models.Model): # время начала действия, остальные что не начали действие - NULL time_start = models.DateTimeField(null=True, blank=True, default=None) + # время завершения услуги + deadline = models.DateTimeField(null=True, blank=True, default=None) + def priority_up(self): # ищем услугу с большим приоритетом(число приоритета меньше) target_abtar = AbonTariff.objects.filter( @@ -108,21 +103,24 @@ class AbonTariff(models.Model): # Считает текущую стоимость услуг согласно выбранной для тарифа логики оплаты (см. в документации) def calc_amount_service(self): - calc_obj = self.tariff.get_calc_type() + calc_obj = self.tariff.get_calc_type()(self) # calc_obj - instance of tariff_app.custom_tariffs.TariffBase - amount = calc_obj.calc_amount(self) + amount = calc_obj.calc_amount() return round(amount, 2) # Активируем тариф def activate(self, current_user): + calc_obj = self.tariff.get_calc_type()(self) amnt = self.calc_amount_service() # если не хватает денег if self.abon.ballance < amnt: raise LogicError('Не хватает денег на счету') - # дата активации услуги + # считаем дату активации услуги self.time_start = timezone.now() + # считаем дату завершения услуги + self.deadline = calc_obj.calc_deadline() # снимаем деньги за услугу - self.abon.make_pay(current_user, amnt) + self.abon.make_pay(current_user, amnt, u_comment='Завершение и оплата услуги по истечению срока действия') self.save() # Используется-ли услуга сейчас, если время старта есть то он активирован @@ -209,7 +207,7 @@ class Abon(UserProfile): self.ballance += amount # покупаем тариф - def buy_tariff(self, tariff, author, comment=None): + def pick_tariff(self, tariff, author, comment=None): assert isinstance(tariff, Tariff) # выбераем связь ТарифАбонент с самым низким приоритетом @@ -241,47 +239,29 @@ class Abon(UserProfile): def activate_next_tariff(self, author): ats = AbonTariff.objects.filter(abon=self).order_by('tariff_priority') - nw = datetime.now(tz=timezone.get_current_timezone()) + nw = timezone.datetime.now() for at in ats: - # Если активированный тариф - if not at.is_started(): - return - - # время к началу месяца - to_start_month = datetime(nw.year, nw.month, 1, tzinfo=timezone.get_current_timezone()) - - # проверяем расстояние от Сегодня до начала этого месяца. - # И от заказа тарифа до начала этого месяца - if (nw - at.time_start) > (nw - to_start_month): - # Заказ из прошлого месяца, срок действия закончен - print('Заказ из прошлого месяца, срок действия закончен') - + # если услуга просрочена + if nw > at.deadline: + print("Услуга просрочена, отключаем, и подключаем новую") # выберем следующую по приоритету # next_tarifs = AbonTariff.objects.filter(tariff_priority__gt = self.tariff_priority, abon=self.abon) - next_tarifs = filter(lambda tr: tr.tariff_priority > at.tariff_priority, ats)[:2] + next_tarifs = [tr for tr in ats if tr.tariff_priority > at.tariff_priority][:2] + #next_tarifs = filter(lambda tr: tr.tariff_priority > at.tariff_priority, ats)[:2] - # и если что-нибудь вернулось то активируем, давая время начала действия + # и если что-нибудь из списка следующих услуг вернулось - то активируем if len(next_tarifs) > 0: - next_tarifs[0].time_start = nw - next_tarifs[0].save(update_fields=['time_start']) + next_tarifs[0].activate(author) - # завершаем текущую услугу. + # удаляем запись о текущей услугу. at.delete() - # Создаём лог о завершении услуги - AbonLog.objects.create( - abon=self, - amount=0, - author=author, - comment='Завершение услуги по истечению срока действия' - ) - # есть-ли доступ у абонента к услуге, смотрим в tariff_app.custom_tariffs..manage_access() def is_access(self): trf = self.active_tariff() if not trf: return False - ct = trf.get_calc_type() + ct = trf.get_calc_type()() if ct.manage_access(self): return True else: @@ -311,7 +291,7 @@ class InvoiceForPayment(models.Model): author = models.ForeignKey(UserProfile, related_name='+') def __str__(self): - return "%s -> %d $" % (self.abon.username, self.amount) + return "%s -> %.2f" % (self.abon.username, self.amount) def set_ok(self): self.status = True diff --git a/abonapp/urls_abon.py b/abonapp/urls_abon.py index 45fcf77..730e3b8 100644 --- a/abonapp/urls_abon.py +++ b/abonapp/urls_abon.py @@ -1,5 +1,4 @@ from django.conf.urls import url - from . import views @@ -14,7 +13,7 @@ urlpatterns = [ url(r'^(?P\d+)/pay_history', views.pay_history, name='abon_phistory'), url(r'^(?P\d+)/addinvoice$', views.add_invoice, name='add_invoice'), - url(r'^(?P\d+)/buy$', views.buy_tariff, name='buy_tariff'), + url(r'^(?P\d+)/pick$', views.pick_tariff, name='pick_tariff'), url(r'^(?P\d+)/chpriority$', views.chpriority, name='chpriority_tariff'), url(r'^(?P\d+)/complete_service(?P\d+)$', views.complete_service, name='compl_srv'), url(r'^(?P\d+)/activate_service(?P\d+)$', views.activate_service, name='activate_service'), diff --git a/abonapp/views.py b/abonapp/views.py index 26d0f2f..2b04f05 100644 --- a/abonapp/views.py +++ b/abonapp/views.py @@ -285,6 +285,7 @@ def abonhome(request, gid, uid): }) +@mydefs.require_ssl def terminal_pay(request): from .pay_systems import allpay ret_text = allpay(request) @@ -329,7 +330,7 @@ def add_invoice(request, gid, uid): @login_required @permission_required('abonapp.can_buy_tariff') -def buy_tariff(request, gid, uid): +def pick_tariff(request, gid, uid): frm = None grp = get_object_or_404(models.AbonGroup, id=gid) abon = get_object_or_404(models.Abon, id=uid) @@ -338,7 +339,7 @@ def buy_tariff(request, gid, uid): frm = forms.BuyTariff(request.POST) if frm.is_valid(): cd = frm.cleaned_data - abon.buy_tariff(cd['tariff'], request.user) + abon.pick_tariff(cd['tariff'], request.user) #abon.save() messages.success(request, 'Тариф успешно выбран') return redirect('abonapp:abon_services', gid=gid, uid=abon.id) @@ -433,7 +434,7 @@ def activate_service(request, gid, uid, srvid): abtar.activate(request.user) messages.success(request, 'Услуга активирована') - return redirect('abonapp:abon_home', gid, uid) + return redirect('abonapp:abon_services', gid, uid) except NasFailedResult as e: messages.error(request, e) @@ -441,12 +442,14 @@ def activate_service(request, gid, uid, srvid): messages.warning(request, e) except models.LogicError as e: messages.error(request, e) + calc_obj = abtar.tariff.get_calc_type()(abtar) return render(request, 'abonapp/activate_service.html', { 'abon': abtar.abon, 'abon_group': abtar.abon.group, 'abtar': abtar, 'amount': amount, - 'diff': abtar.abon.ballance - amount + 'diff': abtar.abon.ballance - amount, + 'deadline': calc_obj.calc_deadline() }) diff --git a/clientsideapp/views.py b/clientsideapp/views.py index 63e48bf..bd04e14 100644 --- a/clientsideapp/views.py +++ b/clientsideapp/views.py @@ -45,7 +45,7 @@ def buy_service(request, srv_id): service = get_object_or_404(Tariff, id=srv_id) current_service = abon.active_tariff() if request.method == 'POST': - abon.buy_tariff(service, request.user, 'Покупка тарифного плана через личный кабинет, тариф "%s"' + abon.pick_tariff(service, request.user, 'Покупка тарифного плана через личный кабинет, тариф "%s"' % service) messages.success(request, 'Вы подписались на новую услугу. Она встала на очередь подключений. ' 'Когда закончится ваша текущая услуга, то включится эта') diff --git a/cron.py b/cron.py index ff04abb..b1c19a0 100755 --- a/cron.py +++ b/cron.py @@ -2,14 +2,13 @@ # -*- coding: utf-8 -*- import os import django +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djing.settings") +django.setup() +from abonapp.models import Abon, LogicError +from agent import Transmitter, NasNetworkError, NasFailedResult -if __name__ == "__main__": - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djing.settings") - django.setup() - from abonapp.models import Abon - from agent import Transmitter - +def main(): tm = Transmitter() # получим инфу о записях в NAS @@ -17,45 +16,63 @@ if __name__ == "__main__": users = Abon.objects.all() for user in users: + try: + # бдим за услугами абонента: просроченные отключить, заказанные подключить + user.activate_next_tariff(user) + + # если нет ip то и нет смысла лезть в NAS + if user.ip_address is None: + continue + + # а есть-ли у абонента доступ к услуге + if not user.is_access(): + continue - # если нет ip то и нет смысла лезть в NAS - if user.ip_address is None: - continue - - # а есть-ли у абонента доступ к услуге - if not user.is_access(): - continue - - # строим структуру агента - ab = user.build_agent_struct() - if ab is None: - # если не построилась структура агента, значит нет ip - # а если нет ip то и синхронизировать абонента без ip нельзя - continue - - # ищем абонента в списке инфы из nas - abons = [queue for queue in queues if queue is not None] - abons = [{'abon': queue.abon, 'mikro_id': queue.sid} for queue in queues if queue.abon.uid == user.pk] - abons_len = len(abons) - if abons_len < 1: - # абонент не найден в nas, добавим - tm.add_user(ab) - continue - elif abons_len > 1: - # удаляем срез из nas, всё кроме 1й записи - tm.remove_user_range( - [mkid['mikro_id'] for mkid in abons[1:]] - ) - # один абонент - # сравним совпадает-ли инфа об абоненте в базе и в nas - if ab == abons[0]['abon']: - # если всё совпадает, то менять нечего - continue - else: - # иначе обновляем абонента - tm.update_user(ab, abons[0]['mikro_id']) - # если не активен то приостановим услугу - if user.is_active: - tm.start_user(abons[0]['mikro_id']) + # строим структуру агента + ab = user.build_agent_struct() + if ab is None: + # если не построилась структура агента, значит нет ip + # а если нет ip то и синхронизировать абонента без ip нельзя + continue + + # ищем абонента в списке инфы из nas + abons = [queue for queue in queues if queue is not None] + abons = [{'abon': queue.abon, 'mikro_id': queue.sid} for queue in abons if queue.abon.uid == user.pk] + abons_len = len(abons) + if abons_len < 1: + # абонент не найден в nas, добавим + tm.add_user(ab) + continue + elif abons_len > 1: + # удаляем срез из nas, всё кроме 1й записи + tm.remove_user_range( + [mkid['mikro_id'] for mkid in abons[1:]] + ) + # один абонент + # сравним совпадает-ли инфа об абоненте в базе и в nas + if ab == abons[0]['abon']: + # если всё совпадает, то менять нечего + continue else: - tm.pause_user(abons[0]['mikro_id']) + # иначе обновляем абонента + tm.update_user(ab, abons[0]['mikro_id']) + # если не активен то приостановим услугу + if user.is_active: + tm.start_user(abons[0]['mikro_id']) + else: + tm.pause_user(abons[0]['mikro_id']) + except NasNetworkError as er: + print("Error:", er) + except NasFailedResult as er: + print("Error:", er) + except LogicError as er: + print("Notice:", er) + + +if __name__ == "__main__": + try: + main() + except NasNetworkError as e: + print(e) + except NasFailedResult as e: + print(e) diff --git a/mydefs.py b/mydefs.py index e122e32..51e7256 100644 --- a/mydefs.py +++ b/mydefs.py @@ -5,11 +5,12 @@ import socket import struct from collections import Iterator import os -from django.http import HttpResponse, Http404 +from functools import wraps +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 +from djing.settings import PAGINATION_ITEMS_PER_PAGE, DEBUG 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]?)$' @@ -138,6 +139,7 @@ def order_helper(request): # Декоратор проверяет аккаунт, чтоб не пускать клиентов в страницы администрации def only_admins(fn): + @wraps(fn) def wrapped(request, *args, **kwargs): if request.user.is_admin: return fn(request, *args, **kwargs) @@ -180,3 +182,18 @@ class RuTimedelta(timedelta): ru_days = 'день' text_date = '%d %s %s' % (self.days, ru_days, text_date) return text_date + + +def require_ssl(view): + """ + Decorator that requires an SSL connection. If the current connection is not SSL, we redirect to the SSL version of + the page. + from: https://gist.github.com/ckinsey/9709984 + """ + @wraps(view) + def wrapper(request, *args, **kwargs): + if not DEBUG and not request.is_secure(): + target_url = "https://" + request.META['HTTP_HOST'] + request.path_info + return HttpResponseRedirect(target_url) + return view(request, *args, **kwargs) + return wrapper diff --git a/tariff_app/base_intr.py b/tariff_app/base_intr.py index 2014a5e..caa6723 100644 --- a/tariff_app/base_intr.py +++ b/tariff_app/base_intr.py @@ -5,12 +5,12 @@ from abc import ABCMeta, abstractmethod class TariffBase(metaclass=ABCMeta): @abstractmethod - def calc_amount(self, abon_tariff): + def calc_amount(self): """Считает итоговую сумму платежа""" @abstractmethod - def get_avail_time(self): - """Возвращает оставшееся время услуги в секундах""" + def calc_deadline(self): + """Считаем дату, до которой действкет тариф""" @staticmethod def description(): diff --git a/tariff_app/custom_tariffs.py b/tariff_app/custom_tariffs.py index b4f0abe..7cef333 100644 --- a/tariff_app/custom_tariffs.py +++ b/tariff_app/custom_tariffs.py @@ -1,42 +1,46 @@ # -*- coding: utf-8 -*- -from datetime import datetime, timedelta +from datetime import timedelta from django.utils import timezone from .base_intr import TariffBase +from calendar import monthrange # from abonapp import AbonTariff class TariffDefault(TariffBase): - # Базовый функционал считает стоимость пропорционально использованному времени - def calc_amount(self, abon_tariff): + + def __init__(self, abon_tariff): #assert isinstance(abon_tariff, AbonTariff) + self.abon_tariff = abon_tariff + + # Базовый функционал считает стоимость пропорционально использованному времени + def calc_amount(self): # сейчас nw = timezone.now() # сколько прошло с начала действия услуги # если времени начала нет то это начало действия, использованное время 0 - time_diff = nw - abon_tariff.time_start if abon_tariff.time_start else timedelta(0) + time_diff = nw - self.abon_tariff.time_start if self.abon_tariff.time_start else timedelta(0) # времени в этом месяце - curr_month_time = datetime(nw.year, nw.month if nw.month == 12 else nw.month + 1, 1) - timedelta(days=1) + curr_month_time = timezone.datetime(nw.year, nw.month if nw.month == 12 else nw.month + 1, 1) - timedelta(days=1) curr_month_time = timedelta(days=curr_month_time.day) # Сколько это в процентах от всего месяца (k - коеффициент) k = time_diff.total_seconds() / curr_month_time.total_seconds() # результат - это полная стоимость тарифа умноженная на k - res = k * abon_tariff.tariff.amount + res = k * self.abon_tariff.tariff.amount return float(res) - # возвращаем сколько времени осталось до завершения услуги (конца месяца) - def get_avail_time(self): - from calendar import monthrange + # Тут мы расчитываем конец действия услуги, завершение будет в конце месяца + def calc_deadline(self): nw = timezone.now() last_day = monthrange(nw.year, nw.month)[1] - last_month_date = datetime(year=nw.year, month=nw.month, day=last_day, - hour=23,minute=59, second=59,tzinfo=nw.tzinfo) - return last_month_date - nw + last_month_date = timezone.datetime(year=nw.year, month=nw.month, day=last_day, + hour=23, minute=59, second=59) + return timezone.make_aware(last_month_date) @staticmethod def description(): @@ -47,8 +51,8 @@ class TariffDp(TariffDefault): # в IS снимается вся стоимость тарифа вне зависимости от времени использования # просто возвращаем всю стоимость тарифа - def calc_amount(self, abon_tariff): - return float(abon_tariff.tariff.amount) + def calc_amount(self): + return float(self.abon_tariff.tariff.amount) @staticmethod def description(): @@ -56,7 +60,7 @@ class TariffDp(TariffDefault): class TariffCp(TariffDefault): - def calc_amount(self, abon_tariff): + def calc_amount(self): return 12.6 @staticmethod diff --git a/tariff_app/models.py b/tariff_app/models.py index 411f5c0..508276a 100644 --- a/tariff_app/models.py +++ b/tariff_app/models.py @@ -28,7 +28,7 @@ class Tariff(models.Model): if len(ob) > 0: res_type = ob[0][1] assert issubclass(res_type, TariffBase) - return res_type() + return res_type def __str__(self): return "%s (%.2f)" % (self.title, self.amount) diff --git a/templates/abonapp/activate_service.html b/templates/abonapp/activate_service.html index 14c2827..bfa6dfd 100644 --- a/templates/abonapp/activate_service.html +++ b/templates/abonapp/activate_service.html @@ -24,7 +24,9 @@

Вы уверены что хотите активировать абоненту эту услугу?
Обратите внимание что с его счёта снимутся деньги и откроется доступ к ресурсам оплаченной услуги.
- Стоимость услуги: {{ amount }}руб. На счету {{ abon.ballance }} руб, останется {{ diff }} руб.

+ Стоимость услуги: {{ amount }}руб. На счету {{ abon.ballance }} руб, останется {{ diff }} руб.
+ Услуга будет действовать до {{ deadline|date:'d F Y, H:i:s' }} +