{% for dev in devs %}
-
+
{{ dev.comment }}
{% empty %}
diff --git a/photo_app/models.py b/photo_app/models.py
index edc9748..5f33500 100644
--- a/photo_app/models.py
+++ b/photo_app/models.py
@@ -27,9 +27,7 @@ class Photo(models.Model):
def save(self, *args, **kwargs):
if not self.image:
return
-
- super().save()
-
+ super(Photo, self).save()
im = Image.open(self.image.path)
im.thumbnail((759, 759), Image.ANTIALIAS)
@@ -44,8 +42,7 @@ class Photo(models.Model):
im.save(fname)
os.remove(self.image.path)
self.image = "%s.%s" % (hs, ext)
- super().save()
-
+ super(Photo, self).save()
# class Meta:
# unique_together = (('image',),)
diff --git a/queue_mngr.py b/queue_mngr.py
old mode 100644
new mode 100755
index 4faefa3..a5b7835
--- a/queue_mngr.py
+++ b/queue_mngr.py
@@ -2,6 +2,7 @@
import os
import sys
from rq import Connection, Worker
+from pid.decorator import pidfile
import django
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djing.settings")
from agent import NasFailedResult, NasNetworkError
@@ -11,7 +12,8 @@ from django.core.exceptions import ValidationError
"""
Заустить этот скрипт как демон, он соединяет redis и django
"""
-if __name__ == '__main__':
+@pidfile()
+def main():
try:
django.setup()
with Connection():
@@ -22,3 +24,7 @@ if __name__ == '__main__':
print('NAS:', e)
except (ValidationError, ValueError) as e:
print('ERROR:', e)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/requirements.txt b/requirements.txt
index 952569a..327d758 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -8,3 +8,4 @@ xmltodict
mysqlclient
easysnmp
rq
+pid
diff --git a/searchapp/views.py b/searchapp/views.py
index e9155fe..4e5a8d4 100644
--- a/searchapp/views.py
+++ b/searchapp/views.py
@@ -4,12 +4,14 @@ from django.shortcuts import render
from django.utils.html import escape
from abonapp.models import Abon
from mydefs import ip_addr_regex
+from django.contrib.auth.decorators import login_required
def replace_without_case(orig, old, new):
return re.sub(old, new, orig, flags=re.IGNORECASE)
+@login_required
def home(request):
s = request.GET.get('s')
s = s.replace('+', '')
diff --git a/static/css/custom.css b/static/css/custom.css
index 78c1330..d698291 100644
--- a/static/css/custom.css
+++ b/static/css/custom.css
@@ -138,6 +138,7 @@ td.btn-group {
border-radius: 3px;
padding: 6px;
width: 79px;
+ font-size: x-small;
}
.port .port-img{
background-image: url(/static/img/icon-port-64x64-grey.png);
@@ -155,6 +156,9 @@ a.port-img{
text-decoration: none;
}
+/* 10GBit/s */
+.port.ten{background-color: #a4fff7;}
+
/* 1GBit/s */
.port.giga{background-color: #caccfb;}
@@ -242,8 +246,7 @@ button[data-toggle=offcanvas]{
fill-opacity: 0.3;
}
+a.navbar-brand {
+ padding-left: 88px;
+}
-/*
- * Сужаем аудио элемент чтоб скрыть большинство контролов
- */
-audio{width: 100px;}
diff --git a/static/js/my.js b/static/js/my.js
index d7be95e..26f2a23 100644
--- a/static/js/my.js
+++ b/static/js/my.js
@@ -27,7 +27,7 @@ $(document).ajaxError(function (ev, jqXHR, ajaxSettings, thrownError) {
$.fn.selectajax = function (opt) {
var settings = $.extend( {
- url : '/api'
+ url : this.attr('data-dst')
}, opt);
var selectbtn = this.children('button.selectajax-btn');
@@ -40,7 +40,6 @@ $(document).ajaxError(function (ev, jqXHR, ajaxSettings, thrownError) {
var hr = a.attr('href');
var tx = a.text();
selecthid.val(hr.substr(1));
- console.debug(tx);
selectbtn.text(tx).removeClass('hidden');
selectinp.addClass('hidden').val(tx);
};
@@ -49,7 +48,7 @@ $(document).ajaxError(function (ev, jqXHR, ajaxSettings, thrownError) {
$.getJSON(settings.url, {'s': this.value}, function (r) {
selectul.empty();
r.forEach(function (o) {
- var li = $('
' + o.name + ": " + o.fio + '');
+ var li = $('
' + o.text + '');
selectul.append(li);
li.on('click', selectajax_click)
});
@@ -70,16 +69,55 @@ $(document).ajaxError(function (ev, jqXHR, ajaxSettings, thrownError) {
})(jQuery);
-$(document).ready(function () {
+// AudioPlayer
+(function ($) {
+ $.fn.aplayer = function(){
- // ajax tabs
- $('.nav-tabs a').on('show.bs.tab', function (e) {
- var ct = $(e.target).attr('href');
- var remoteUrl = $(this).attr('data-tab-remote');
- if (remoteUrl !== '') {
- $(ct).load(remoteUrl);
- }
- });
+ var def_play = function(e){
+ var audiotag = e.data['audiotag'][0];
+
+ if(audiotag.readyState == 0){
+ $(this).prop('disabled', true);
+ return;
+ }else
+ $(this).prop('disabled', false);
+
+ if(audiotag.paused)
+ audiotag.play();
+ else
+ audiotag.pause();
+ };
+
+ var def_canplay = function(){
+ var els = $(this).parent();
+ els.prop('disabled', false).removeClass('disabled');
+ els.siblings().prop('disabled', false).removeClass('disabled');
+ };
+
+ var def_on_play = function(){
+ $(this).siblings('span.glyphicon').attr('class', 'glyphicon glyphicon-pause');
+ };
+
+ var def_on_pause = function(){
+ $(this).siblings('span.glyphicon').attr('class', 'glyphicon glyphicon-play');
+ };
+
+ this.each(function(){
+ var i = $(this);
+ var audiotag = i.children('audio');
+ var icon = i.children('span.glyphicon');
+ i.on('click', {'audiotag': audiotag}, def_play);
+ audiotag.on('canplay', def_canplay);
+ audiotag.on('play', def_on_play);
+ audiotag.on('pause', def_on_pause);
+ });
+
+ };
+})(jQuery);
+
+
+
+$(document).ready(function () {
// Live html5 image preview
if (window.File && window.FileReader && window.FileList && window.Blob) {
@@ -114,9 +152,7 @@ $(document).ready(function () {
});
- $('div.selectajax').selectajax({
- url: '/abons/api/abon_filter'
- });
+ $('div.selectajax').selectajax();
$('[data-toggle=offcanvas]').click(function () {
$('.row-offcanvas').toggleClass('active');
@@ -147,6 +183,10 @@ $(document).ready(function () {
self.html(r.dat);
});
return false;
- })
+ });
+
+ $('button.player-btn').aplayer();
+
+ $('[data-toggle="tooltip"]').tooltip({container:'body'});
});
diff --git a/statistics/migrations/0002_delete_statelem.py b/statistics/migrations/0002_delete_statelem.py
deleted file mode 100644
index 8fd4fbe..0000000
--- a/statistics/migrations/0002_delete_statelem.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-# Generated by Django 1.9 on 2017-04-25 13:27
-from __future__ import unicode_literals
-
-from django.db import migrations
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('statistics', '0001_initial'),
- ]
-
- operations = [
- migrations.DeleteModel(
- name='StatElem',
- ),
- ]
diff --git a/statistics/migrations/0002_statcache.py b/statistics/migrations/0002_statcache.py
new file mode 100644
index 0000000..391ea4e
--- /dev/null
+++ b/statistics/migrations/0002_statcache.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9 on 2017-05-31 17:37
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import mydefs
+import statistics.fields
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ('statistics', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='StatCache',
+ fields=[
+ ('last_time', statistics.fields.UnixDateTimeField()),
+ ('ip', mydefs.MyGenericIPAddressField(max_length=8, primary_key=True, protocol='ipv4', serialize=False)),
+ ('octets', models.PositiveIntegerField(default=0)),
+ ('packets', models.PositiveIntegerField(default=0)),
+ ],
+ options={
+ 'db_table': 'flowcache',
+ },
+ ),
+ migrations.DeleteModel(
+ name='StatElem',
+ ),
+ ]
diff --git a/statistics/models.py b/statistics/models.py
index 6a34c7f..c222edc 100644
--- a/statistics/models.py
+++ b/statistics/models.py
@@ -1,25 +1,19 @@
import math
from datetime import datetime, timedelta, date, time
-from django.db import models, ProgrammingError, connection
+from django.db import models, connection
+from django.utils.timezone import now
+
from mydefs import MyGenericIPAddressField
from .fields import UnixDateTimeField
-from mydefs import LogicError
-class StatManager(models.Manager):
+def get_dates():
+ tables = connection.introspection.table_names()
+ tables = [t.replace('flowstat_', '') for t in tables if t.startswith('flowstat_')]
+ return [datetime.strptime(t, '%d%m%Y').date() for t in tables]
- def traffic_by_ip(self, ip):
- try:
- traf = self.order_by('-cur_time').filter(ip=ip, octets__gt=524288)[0]
- now = datetime.now()
- if traf.cur_time < now - timedelta(minutes=55):
- return False, traf
- else:
- return True, traf
- except IndexError:
- return False, None
- except ProgrammingError as e:
- raise LogicError(e)
+
+class StatManager(models.Manager):
def chart(self, ip_addr, count_of_parts=12, want_date=date.today()):
def byte_to_mbit(x):
@@ -27,6 +21,8 @@ class StatManager(models.Manager):
def split_list(lst, chunk_count):
chunk_size = len(lst) // chunk_count
+ if chunk_size == 0:
+ chunk_size = 1
return [lst[i:i+chunk_size] for i in range(0, len(lst), chunk_size)]
def avarage(elements):
@@ -42,7 +38,7 @@ class StatManager(models.Manager):
charts_times = split_list(charts_times, count_of_parts)
charts_times = [avarage(t) for t in charts_times]
- charts_data = map(lambda x, y: (x, y), charts_times, charts_octets)
+ charts_data = zip(charts_times, charts_octets)
charts_data = ["{x: new Date(%d), y: %.2f}" % (cd[0], cd[1]) for cd in charts_data]
midnight = datetime.combine(want_date, time.min)
charts_data.append("{x:new Date(%d),y:0}" % (int(charts_times[-1:][0]) + 1))
@@ -51,11 +47,6 @@ class StatManager(models.Manager):
else:
return
- def get_dates(self):
- tables = connection.introspection.table_names()
- tables = [t.replace('flowstat_', '') for t in tables if t.startswith('flowstat_')]
- return [datetime.strptime(t, '%d%m%Y').date() for t in tables]
-
class StatElem(models.Model):
cur_time = UnixDateTimeField(primary_key=True)
@@ -65,11 +56,19 @@ class StatElem(models.Model):
objects = StatManager()
+ # ReadOnly
def save(self, *args, **kwargs):
- return
+ pass
+ # ReadOnly
def delete(self, *args, **kwargs):
- return
+ pass
+
+ def delete_month(self):
+ cursor = connection.cursor()
+ table_name = self._meta.db_table
+ sql = "DROP TABLE %s;" % table_name
+ cursor.execute(sql)
@staticmethod
def percentile(N, percent, key=lambda x:x):
@@ -97,10 +96,27 @@ class StatElem(models.Model):
abstract = True
-def getModel(want_date=datetime.now()):
+def getModel(want_date=now()):
class DynamicStatElem(StatElem):
class Meta:
db_table = 'flowstat_%s' % want_date.strftime("%d%m%Y")
abstract = False
return DynamicStatElem
+
+
+class StatCache(models.Model):
+ last_time = UnixDateTimeField()
+ ip = MyGenericIPAddressField(primary_key=True)
+ octets = models.PositiveIntegerField(default=0)
+ packets = models.PositiveIntegerField(default=0)
+
+ def is_online(self):
+ return self.last_time > now() - timedelta(minutes=55)
+
+ def is_today(self):
+ return date.today() == self.last_time.date()
+
+
+ class Meta:
+ db_table = 'flowcache'
diff --git a/systemd_units/djing_queue.service b/systemd_units/djing_queue.service
new file mode 100644
index 0000000..830a585
--- /dev/null
+++ b/systemd_units/djing_queue.service
@@ -0,0 +1,15 @@
+[Unit]
+Description=Djing queue manager
+
+[Service]
+Type=simple
+ExecStart=/usr/bin/python3 ./queue_mngr.py > /dev/null
+PIDFile=/run/queue_mngr.py.pid
+WorkingDirectory=/var/www/djing
+TimeoutSec=15
+Restart=always
+User=http
+Group=http
+
+[Install]
+WantedBy=multi-user.target
diff --git a/systemd_units/djing_telebot.service b/systemd_units/djing_telebot.service
new file mode 100644
index 0000000..e7676af
--- /dev/null
+++ b/systemd_units/djing_telebot.service
@@ -0,0 +1,15 @@
+[Unit]
+Description=Djing telegram bot
+
+[Service]
+Type=simple
+ExecStart=/usr/bin/python3 ./telebot.py > /dev/null
+PIDFile=/run/telebot.py.pid
+WorkingDirectory=/var/www/djing
+TimeoutSec=9
+Restart=always
+User=http
+Group=http
+
+[Install]
+WantedBy=multi-user.target
diff --git a/tariff_app/custom_tariffs.py b/tariff_app/custom_tariffs.py
index 630ce4a..21666b7 100644
--- a/tariff_app/custom_tariffs.py
+++ b/tariff_app/custom_tariffs.py
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
from datetime import timedelta, datetime
from django.utils import timezone
+from django.utils.translation import ugettext as _
from .base_intr import TariffBase
from calendar import monthrange
@@ -44,7 +45,7 @@ class TariffDefault(TariffBase):
@staticmethod
def description():
- return 'Базовый расчётный функционал'
+ return _('Base calculate functionality')
class TariffDp(TariffDefault):
@@ -71,7 +72,7 @@ class TariffCp(TariffDp):
@staticmethod
def description():
- return 'Для внутреннего пользования'
+ return _('Private service')
# Первый - всегда по умолчанию
diff --git a/tariff_app/locale/ru/LC_MESSAGES/django.po b/tariff_app/locale/ru/LC_MESSAGES/django.po
new file mode 100644
index 0000000..3dd5f81
--- /dev/null
+++ b/tariff_app/locale/ru/LC_MESSAGES/django.po
@@ -0,0 +1,103 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# Dmitry Novikov nerosketch@gmail.com, 2017.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2017-06-15 13:44+0300\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Dmitry Novikov nerosketch@gmail.com\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
+"%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n"
+"%100>=11 && n%100<=14)? 2 : 3);\n"
+
+#: tariff_app/templates/tariff_app/modal_del_warning.html:23
+msgid "Delete"
+msgstr "Удалить"
+
+#: tariff_app/templates/tariff_app/modal_del_warning.html:26
+msgid "Reset"
+msgstr "Сбросить"
+
+msgid "Tarifs"
+msgstr "Тарифы"
+
+msgid "Add"
+msgstr "Добавить"
+
+msgid "Edit"
+msgstr "Редактировать"
+
+msgid "Create"
+msgstr "Создать"
+
+msgid "tariff"
+msgstr "тариф"
+
+msgid "Service title"
+msgstr "Название тарифа"
+
+msgid "Service description"
+msgstr "Описание тарифа"
+
+msgid "Speed In"
+msgstr "Входящая скорость"
+
+msgid "Speed Out"
+msgstr "Исходящая скорость"
+
+msgid "Price"
+msgstr "Стоимость"
+
+msgid "Script"
+msgstr "Скрипт"
+
+msgid "Tech service"
+msgstr "Административный тариф"
+
+msgid "Save"
+msgstr "Сохранить"
+
+msgid "Service list"
+msgstr "Список тарифов"
+
+msgid "Services does not exist yet"
+msgstr "Ещё нет созданных тарифов"
+
+msgid "Base calculate functionality"
+msgstr "Базовый расчётный функционал"
+
+msgid "Private service"
+msgstr "Для внутреннего пользования"
+
+msgid "Service has been saved"
+msgstr "Тариф успешно сохранён"
+
+msgid "Some fields were filled incorrect, please try again"
+msgstr "Не все поля заполнены правильно, проверте и попробуйте ещё раз"
+
+msgid "Service has been deleted"
+msgstr "Тарифный план успешно удалён"
+
+msgid "Not have a confirmations of delete"
+msgstr "Нет подтверждения удаления"
+
+msgid "Delete service"
+msgstr "Удалить тарифный план"
+
+msgid "Attention"
+msgstr "Внимание"
+
+msgid ""
+"after delete the tariff, subscribers who use that tariff will be disconnected from it."
+msgstr ""
+"После того как вы удалите тарифный план то абоненты, подписанные на него, сразу потеряют услугу по этому тарифу. "
+"Так что сначала убедитесь что уже никто не пользуется тарифом, и только после этого удалите его."
diff --git a/tariff_app/models.py b/tariff_app/models.py
index 04aa970..0981537 100644
--- a/tariff_app/models.py
+++ b/tariff_app/models.py
@@ -5,21 +5,13 @@ from .custom_tariffs import TariffBase, TARIFF_CHOICES
from mydefs import MyChoicesAdapter
-# Класс похож на адаптер. Предназначен для Django CHOICES чтоб можно было передавать классывместо просто описания поля,
-# классы передавать для того чтоб по значению из базы понять какой класс нужно взять для расчёта стоимости тарифа.
-class _TariffChoicesAdapter(MyChoicesAdapter):
- # На вход принимает кортеж кортежей, вложенный из 2х элементов: кода и класса, как: TARIFF_CHOICES
- def __init__(self):
- super().__init__(TARIFF_CHOICES)
-
-
class Tariff(models.Model):
title = models.CharField(max_length=32)
descr = models.CharField(max_length=256)
speedIn = models.FloatField(default=0.0)
speedOut = models.FloatField(default=0.0)
amount = models.FloatField(default=0.0)
- calc_type = models.CharField(max_length=2, default=TARIFF_CHOICES[0][0], choices=_TariffChoicesAdapter())
+ calc_type = models.CharField(max_length=2, default=TARIFF_CHOICES[0][0], choices=MyChoicesAdapter(TARIFF_CHOICES))
is_admin = models.BooleanField(default=False)
# Возвращает потомок класса TariffBase, методы которого дают нужную логику оплаты по тарифу
diff --git a/tariff_app/templates/tariff_app/editTarif.html b/tariff_app/templates/tariff_app/editTarif.html
index dc86afe..30ebb81 100644
--- a/tariff_app/templates/tariff_app/editTarif.html
+++ b/tariff_app/templates/tariff_app/editTarif.html
@@ -1,15 +1,15 @@
{% extends request.is_ajax|yesno:'bajax.html,base.html' %}
+{% load i18n %}
{% block main %}
-
- - Тарифы
+ - {% trans 'Tarifs' %}
-
{% if tarif_id == 0 %}
- Добавить
+ {% trans 'Add' %}
{% else %}
- Редактировать
+ {% trans 'Edit' %}
{% endif %}
@@ -18,75 +18,75 @@
-
{% if tarif_id == 0 %}Создать{% else %}Редактировать{% endif %} тариф
+ {% if tarif_id == 0 %}{% trans 'Create' %}{% else %}{% trans 'Edit' %}{% endif %} {% trans 'tariff' %}
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/tariff_app/templates/tariff_app/modal_del_warning.html b/tariff_app/templates/tariff_app/modal_del_warning.html
new file mode 100644
index 0000000..fbe951d
--- /dev/null
+++ b/tariff_app/templates/tariff_app/modal_del_warning.html
@@ -0,0 +1,30 @@
+{% load i18n %}
+
+
diff --git a/tariff_app/templates/tariff_app/tarifs.html b/tariff_app/templates/tariff_app/tarifs.html
index 668c0d1..0f76a49 100644
--- a/tariff_app/templates/tariff_app/tarifs.html
+++ b/tariff_app/templates/tariff_app/tarifs.html
@@ -1,51 +1,53 @@
{% extends 'base.html' %}
+{% load i18n %}
{% block main %}
- - Тарифы
+ - {% trans 'Tarifs' %}
{% include 'message_block.html' %}
-
Список тарифов
+
{% trans 'Service list' %}
|
- Тариф
+ {% trans 'tariff' %}
{% if order_by == 'title' %}{% endif %}
|
- Входящая скорость
+ {% trans 'Speed In' %}
{% if order_by == 'speedIn' %}{% endif %}
|
- Исходящая скорость
+ {% trans 'Speed Out' %}
{% if order_by == 'speedOut' %}{% endif %}
|
- Стоимость
+ {% trans 'Price' %}
{% if order_by == 'amount' %}{% endif %}
|
- Название скрипта |
+ {% trans 'Script' %} |
Do |
+ {% with can_ch_trf=perms.tariff_app.change_tariff can_del_trf=perms.tariff_app.delete_tariff %}
{% for tar in tariflist %}
|
- {% if perms.tariff_app.change_tariff %}
+ {% if can_ch_trf %}
{{ tar.title }}
{% else %}
{{ tar.title }}
@@ -53,11 +55,11 @@
|
{{ tar.speedIn }} |
{{ tar.speedOut }} |
- {{ tar.amount }} руб |
+ {{ tar.amount }} {% trans 'currency' %} |
{{ tar.get_calc_type_display }} |
- {% if perms.tariff_app.delete_tariff %}
-
+ {% if can_del_trf %}
+
{% endif %}
@@ -65,13 +67,14 @@
|
{% empty %}
- | Ещё нет созданных тарифов.
+ | {% trans 'Услуги пока не существуют' %}.
{% if perms.tariff_app.add_tariff %}
- Создать
+ {% trans 'Create' %}
{% endif %}
|
{% endfor %}
+ {% endwith %}
{% if perms.tariff_app.add_tariff %}
@@ -79,7 +82,7 @@
|
- Добавить
+ {% trans 'Add' %}
|
@@ -91,4 +94,4 @@
{% include 'toolbar_page.html' with pag=tariflist %}
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/tariff_app/urls.py b/tariff_app/urls.py
index 7df7bf5..48e93b9 100644
--- a/tariff_app/urls.py
+++ b/tariff_app/urls.py
@@ -7,5 +7,5 @@ urlpatterns = [
url(r'^$', views.tarifs, name='home'),
url(r'^(?P\d+)$', views.edit_tarif, name='edit'),
url(r'^add$', views.edit_tarif, name='add'),
- url(r'^del(?P\d+)$', views.del_tarif, name='del')
+ url(r'^del(?P\d+)$', views.del_tarif, name='del')
]
diff --git a/tariff_app/views.py b/tariff_app/views.py
index 91150e4..9b8cd0c 100644
--- a/tariff_app/views.py
+++ b/tariff_app/views.py
@@ -1,5 +1,7 @@
# -*- coding: utf-8 -*-
from django.contrib.auth.decorators import login_required, permission_required
+from django.contrib.gis.shortcuts import render_to_text
+from django.utils.translation import ugettext as _
from django.shortcuts import render, get_object_or_404, redirect
from django.contrib import messages
from django.core.exceptions import PermissionDenied
@@ -45,10 +47,10 @@ def edit_tarif(request, tarif_id=0):
frm = forms.TariffForm(request.POST, instance=tarif)
if frm.is_valid():
frm.save()
- messages.success(request, 'Тариф успешно сохранён')
+ messages.success(request, _('Service has been saved'))
return redirect('tarifs:edit', tarif_id=tarif_id)
else:
- messages.warning(request, 'Не все поля заполнены правильно, проверте и попробуйте ещё раз')
+ messages.warning(request, _('Some fields were filled incorrect, please try again'))
else:
frm = forms.TariffForm(instance=tarif)
@@ -60,7 +62,12 @@ def edit_tarif(request, tarif_id=0):
@login_required
@permission_required('tariff_app.delete_tariff')
-def del_tarif(request, id):
- tar_id = mydefs.safe_int(id)
- get_object_or_404(Tariff, id=tar_id).delete()
- return mydefs.res_success(request, 'tarifs:home')
+def del_tarif(request, tid):
+ if request.method == 'POST':
+ if request.POST.get('confirm') == 'yes':
+ get_object_or_404(Tariff, id=tid).delete()
+ messages.success(request, _('Service has been deleted'))
+ else:
+ messages.error(request, _('Not have a confirmations of delete'))
+ return mydefs.res_success(request, 'tarifs:home')
+ return render_to_text('tariff_app/modal_del_warning.html', {'tid': tid}, request=request)
diff --git a/taskapp/handle.py b/taskapp/handle.py
index be59da7..e31303f 100644
--- a/taskapp/handle.py
+++ b/taskapp/handle.py
@@ -2,41 +2,50 @@
from django.utils.translation import ugettext as _
from chatbot.telebot import send_notify
from chatbot.models import ChatException
+from mydefs import MultipleException
class TaskException(Exception):
pass
-def handle(task, author, recipient, abon_group):
- try:
- dst_account = recipient
- text = _('Task')
- # Если сигнал самому себе то молчим
- if author == recipient:
- return
- # Если задача 'На выполнении' то молчим
- if task.state == 'C':
- return
- # Если задача завершена
- elif task.state == 'F':
- text = _('Task completed')
- # Меняем цель назначения на автора, т.к. при завершении
- # идёт оповещение автору о выполнении
- dst_account = author
- if task.abon is not None:
- fulltext="%s:\n%s\n" % (text, task.abon.get_full_name())
- else:
- fulltext="%s\n" % text
- fulltext += _('locality %s.\n') % abon_group.title
- if task.abon:
- fulltext += _('address %s %s.\ntelephone %s\n') % (
- task.abon.street.name if task.abon.street is not None else '<'+_('not chosen')+'>',
- task.abon.house,
- task.abon.telephone
- )
- fulltext += _('Task type - %s.') % task.get_mode_display() + '\n'
- fulltext += task.descr if task.descr else ''
- send_notify(fulltext, dst_account)
- except ChatException as e:
- raise TaskException(e)
+def handle(task, author, recipients, abon_group):
+ errors = []
+ for recipient in recipients:
+ try:
+ dst_account = recipient
+ text = _('Task')
+ # Если сигнал самому себе то молчим
+ if author == recipient:
+ return
+ # Если задача завершена или провалена
+ elif task.state == 'F' or task.state == 'C':
+ text = _('Task completed')
+ if task.abon is not None:
+ fulltext = "%s:\n%s\n" % (text, task.abon.get_full_name())
+ else:
+ fulltext = "%s\n" % text
+ fulltext += _('locality %s.\n') % abon_group.title
+ if task.abon:
+ fulltext += _('address %s %s.\ntelephone %s\n') % (
+ task.abon.street.name if task.abon.street is not None else '<'+_('not chosen')+'>',
+ task.abon.house,
+ task.abon.telephone
+ )
+ fulltext += _('Task type - %s.') % task.get_mode_display() + '\n'
+ fulltext += task.descr if task.descr else ''
+
+ print('task.state:', task.state)
+
+ if task.state == 'F' or task.state == 'C':
+ # Если задача завершена или провалена то отправляем одно оповещение автору
+ try:
+ send_notify(fulltext, author)
+ except ChatException as e:
+ raise TaskException(e)
+ else:
+ send_notify(fulltext, dst_account)
+ except ChatException as e:
+ errors.append(e)
+ if len(errors) > 0:
+ raise MultipleException(errors)
diff --git a/taskapp/handle.sh b/taskapp/handle.sh
index e08b533..8ac380f 100755
--- a/taskapp/handle.sh
+++ b/taskapp/handle.sh
@@ -44,6 +44,6 @@ fi
FULLTEXT="$text: $ABON_FIO. $ABON_ADDR $ABON_TEL. $ABON_GRP. $FAIL_MODE. $DESCR"
-echo "TO $RECIPIENT_TEL: $FULLTEXT" >> /tmp/task_sms.log
+echo "TO $RECIPIENT_TEL: $FULLTEXT"
/usr/bin/gammu-smsd-inject EMS $RECIPIENT_TEL -text "$FULLTEXT" -unicode
diff --git a/taskapp/locale/ru/LC_MESSAGES/django.po b/taskapp/locale/ru/LC_MESSAGES/django.po
index a80eae4..019c4d3 100644
--- a/taskapp/locale/ru/LC_MESSAGES/django.po
+++ b/taskapp/locale/ru/LC_MESSAGES/django.po
@@ -31,7 +31,10 @@ msgid "locality %s.\n"
msgstr "с. %s\n"
#: taskapp/handle.py:33
-msgid "address %s %s.\ntelephone %s\n"
+#, python-format
+msgid ""
+"address %s %s.\n"
+"telephone %s\n"
msgstr "по адресу %s %s тел. %s.\n"
#: taskapp/handle.py:34 taskapp/models.py:26
@@ -39,6 +42,7 @@ msgid "not chosen"
msgstr "не выбрано"
#: taskapp/handle.py:38
+#, python-format
msgid "Task type - %s."
msgstr "Тип задачи - %s."
@@ -59,8 +63,8 @@ msgid "New"
msgstr "Новая"
#: taskapp/models.py:21
-msgid "In fulfilling"
-msgstr "На выполнении"
+msgid "Confused"
+msgstr "Провалена"
#: taskapp/models.py:22
msgid "Completed"
@@ -123,8 +127,8 @@ msgid "Completing tasks"
msgstr "Завершение задачи"
#: taskapp/models.py:47
-msgid "The task started"
-msgstr "Задача начата"
+msgid "The task failed"
+msgstr "Задача провалена"
#: taskapp/models.py:78
msgid "Access to all tasks"
@@ -141,11 +145,11 @@ msgid "Tasks"
msgstr "Задачи"
#: taskapp/templates/taskapp/add_edit_task.html:8
-#: taskapp/templates/taskapp/tasklist.html:48
-#: taskapp/templates/taskapp/tasklist_active.html:46
-#: taskapp/templates/taskapp/tasklist_all.html:54
-#: taskapp/templates/taskapp/tasklist_finish.html:43
-#: taskapp/templates/taskapp/tasklist_own.html:40
+#: taskapp/templates/taskapp/tasklist.html:60
+#: taskapp/templates/taskapp/tasklist_active.html:57
+#: taskapp/templates/taskapp/tasklist_all.html:72
+#: taskapp/templates/taskapp/tasklist_finish.html:54
+#: taskapp/templates/taskapp/tasklist_own.html:53
msgid "Edit"
msgstr "Редактировать"
@@ -156,17 +160,17 @@ msgstr "Создать"
#: taskapp/templates/taskapp/add_edit_task.html:15
#: taskapp/templates/taskapp/footer_btns.html:3
#: taskapp/templates/taskapp/footer_btns.html:4
-#: taskapp/templates/taskapp/tasklist_all.html:75
-#: taskapp/templates/taskapp/tasklist_all.html:76
+#: taskapp/templates/taskapp/tasklist_all.html:93
+#: taskapp/templates/taskapp/tasklist_all.html:94
msgid "Add new task"
msgstr "Добавьте новую задачу"
#: taskapp/templates/taskapp/add_edit_task.html:27
-#: taskapp/templates/taskapp/tasklist.html:10
-#: taskapp/templates/taskapp/tasklist_active.html:10
-#: taskapp/templates/taskapp/tasklist_all.html:22
-#: taskapp/templates/taskapp/tasklist_finish.html:10
-#: taskapp/templates/taskapp/tasklist_own.html:10
+#: taskapp/templates/taskapp/tasklist.html:13
+#: taskapp/templates/taskapp/tasklist_active.html:13
+#: taskapp/templates/taskapp/tasklist_all.html:25
+#: taskapp/templates/taskapp/tasklist_finish.html:13
+#: taskapp/templates/taskapp/tasklist_own.html:13
#: taskapp/templates/taskapp/view.html:19
msgid "Description"
msgstr "Описание"
@@ -176,30 +180,24 @@ msgstr "Описание"
#: taskapp/templates/taskapp/tasklist_active.html:12
#: taskapp/templates/taskapp/tasklist_all.html:24
#: taskapp/templates/taskapp/tasklist_finish.html:12
-#: taskapp/templates/taskapp/tasklist_own.html:11
+#: taskapp/templates/taskapp/tasklist_own.html:12
msgid "The nature of the damage"
msgstr "Характер поломки"
#: taskapp/templates/taskapp/add_edit_task.html:43
-#: taskapp/templates/taskapp/tasklist.html:14
-#: taskapp/templates/taskapp/tasklist_active.html:14
-#: taskapp/templates/taskapp/tasklist_all.html:26
-#: taskapp/templates/taskapp/tasklist_finish.html:14
-#: taskapp/templates/taskapp/tasklist_own.html:13
+#: taskapp/templates/taskapp/view.html:29
msgid "A priority"
msgstr "Приоритет"
#: taskapp/templates/taskapp/add_edit_task.html:51
-#: taskapp/templates/taskapp/tasklist.html:13
-#: taskapp/templates/taskapp/tasklist_active.html:13
-#: taskapp/templates/taskapp/tasklist_all.html:25
-#: taskapp/templates/taskapp/tasklist_finish.html:13
-#: taskapp/templates/taskapp/tasklist_own.html:12
+#: taskapp/templates/taskapp/tasklist_all.html:27
+#: taskapp/templates/taskapp/tasklist_own.html:14
+#: taskapp/templates/taskapp/view.html:34
msgid "Condition"
msgstr "Состояние"
#: taskapp/templates/taskapp/add_edit_task.html:59
-#: taskapp/templates/taskapp/view.html:30
+#: taskapp/templates/taskapp/view.html:35
msgid "Subscriber"
msgstr "Абонент"
@@ -207,15 +205,15 @@ msgstr "Абонент"
msgid "Reality (the date by which you must complete the task)"
msgstr "Актуальность (дата, до которой нужно завершить задачу)"
-#: taskapp/templates/taskapp/add_edit_task.html:91
+#: taskapp/templates/taskapp/add_edit_task.html:90
msgid "Attached image"
msgstr "Прикреплённое изображение"
-#: taskapp/templates/taskapp/add_edit_task.html:99
+#: taskapp/templates/taskapp/add_edit_task.html:98
msgid "Save"
msgstr "Сохранить"
-#: taskapp/templates/taskapp/add_edit_task.html:102
+#: taskapp/templates/taskapp/add_edit_task.html:101
msgid "Reset"
msgstr "Сбросить"
@@ -223,52 +221,78 @@ msgstr "Сбросить"
msgid "View all tasks"
msgstr "Просмотреть все задачи"
+#: taskapp/templates/taskapp/tasklist.html:10
+#: taskapp/templates/taskapp/tasklist_active.html:10
+#: taskapp/templates/taskapp/tasklist_all.html:22
+#: taskapp/templates/taskapp/tasklist_finish.html:10
+#: taskapp/templates/taskapp/tasklist_own.html:10
+msgid "Name"
+msgstr "ФИО"
+
#: taskapp/templates/taskapp/tasklist.html:11
#: taskapp/templates/taskapp/tasklist_active.html:11
#: taskapp/templates/taskapp/tasklist_all.html:23
#: taskapp/templates/taskapp/tasklist_finish.html:11
+#: taskapp/templates/taskapp/tasklist_own.html:11
+msgid "Address"
+msgstr "Адрес"
+
+#: taskapp/templates/taskapp/tasklist.html:14
+#: taskapp/templates/taskapp/tasklist_active.html:14
+#: taskapp/templates/taskapp/tasklist_all.html:26
+#: taskapp/templates/taskapp/tasklist_finish.html:14
+#: taskapp/templates/taskapp/view.html:21
msgid "Task author"
msgstr "Кто назначил"
#: taskapp/templates/taskapp/tasklist.html:15
#: taskapp/templates/taskapp/tasklist_active.html:15
-#: taskapp/templates/taskapp/tasklist_all.html:27
+#: taskapp/templates/taskapp/tasklist_all.html:28
#: taskapp/templates/taskapp/tasklist_finish.html:15
-#: taskapp/templates/taskapp/tasklist_own.html:14
-#: taskapp/templates/taskapp/view.html:27
+#: taskapp/templates/taskapp/tasklist_own.html:15
+#: taskapp/templates/taskapp/view.html:31
msgid "Date of create"
msgstr "Дата создания"
#: taskapp/templates/taskapp/tasklist.html:16
#: taskapp/templates/taskapp/tasklist_active.html:16
-#: taskapp/templates/taskapp/tasklist_all.html:28
-#: taskapp/templates/taskapp/tasklist_finish.html:16
-#: taskapp/templates/taskapp/tasklist_own.html:15
-#: taskapp/templates/taskapp/view.html:37
-msgid "Attachment"
-msgstr "Приложение"
-
-#: taskapp/templates/taskapp/tasklist.html:17
-#: taskapp/templates/taskapp/tasklist_active.html:17
#: taskapp/templates/taskapp/tasklist_all.html:29
-#: taskapp/templates/taskapp/tasklist_finish.html:17
+#: taskapp/templates/taskapp/tasklist_finish.html:16
#: taskapp/templates/taskapp/tasklist_own.html:16
msgid "Actions"
msgstr "Действия"
-#: taskapp/templates/taskapp/tasklist.html:41
-msgid "Begin"
-msgstr "Начать"
+#: taskapp/templates/taskapp/tasklist.html:40
+#: taskapp/templates/taskapp/tasklist_active.html:40
+#: taskapp/templates/taskapp/tasklist_all.html:53
+#: taskapp/templates/taskapp/tasklist_all.html:64
+#: taskapp/templates/taskapp/tasklist_finish.html:40
+#: taskapp/templates/taskapp/tasklist_own.html:40
+#: taskapp/templates/taskapp/view.html:39
+msgid "Not assigned"
+msgstr "<Не назначено>"
-#: taskapp/templates/taskapp/tasklist.html:44
+#: taskapp/templates/taskapp/tasklist.html:42
#: taskapp/templates/taskapp/tasklist_active.html:42
+#: taskapp/templates/taskapp/tasklist_all.html:55
+#: taskapp/templates/taskapp/tasklist_finish.html:42
+#: taskapp/templates/taskapp/tasklist_own.html:42 taskapp/views.py:151
+msgid "User does not exist"
+msgstr "Абонент не найден"
+
+#: taskapp/templates/taskapp/tasklist.html:53
+msgid "Mark as unsuccessful"
+msgstr "Отметить задачу как проваленную"
+
+#: taskapp/templates/taskapp/tasklist.html:56
+#: taskapp/templates/taskapp/tasklist_active.html:53
msgid "Complete"
msgstr "Завершить"
-#: taskapp/templates/taskapp/tasklist.html:56
-#: taskapp/templates/taskapp/tasklist_active.html:54
-#: taskapp/templates/taskapp/tasklist_all.html:67
-#: taskapp/templates/taskapp/tasklist_finish.html:56
+#: taskapp/templates/taskapp/tasklist.html:68
+#: taskapp/templates/taskapp/tasklist_active.html:65
+#: taskapp/templates/taskapp/tasklist_all.html:85
+#: taskapp/templates/taskapp/tasklist_finish.html:67
msgid "The list is empty"
msgstr "Список пуст"
@@ -280,17 +304,17 @@ msgstr "Все задачи"
msgid "Records of all the tasks in the system"
msgstr "Лог всех задач в системе"
-#: taskapp/templates/taskapp/tasklist_all.html:59
-#: taskapp/templates/taskapp/tasklist_own.html:45
+#: taskapp/templates/taskapp/tasklist_all.html:77
+#: taskapp/templates/taskapp/tasklist_own.html:58
msgid "Remind"
msgstr "Напомнить"
-#: taskapp/templates/taskapp/tasklist_finish.html:48
-#: taskapp/templates/taskapp/tasklist_own.html:50
+#: taskapp/templates/taskapp/tasklist_finish.html:59
+#: taskapp/templates/taskapp/tasklist_own.html:63
msgid "Delete"
msgstr "Удалить"
-#: taskapp/templates/taskapp/tasklist_own.html:58
+#: taskapp/templates/taskapp/tasklist_own.html:71
msgid "All your tasks has been performed"
msgstr "Все ваши задачи выполнены"
@@ -298,25 +322,25 @@ msgstr "Все ваши задачи выполнены"
msgid "Task description"
msgstr "Описание задачи"
-#: taskapp/templates/taskapp/view.html:20
+#: taskapp/templates/taskapp/view.html:23
msgid "Implementers"
msgstr "Исполнители"
-#: taskapp/templates/taskapp/view.html:26
+#: taskapp/templates/taskapp/view.html:30
msgid "The task is valid until"
msgstr "Задача действительна до"
-#: taskapp/templates/taskapp/view.html:28
+#: taskapp/templates/taskapp/view.html:32
msgid "time left"
msgstr "Времени осталось"
-#: taskapp/templates/taskapp/view.html:29
+#: taskapp/templates/taskapp/view.html:33
msgid "Task type"
msgstr "Тип задачи"
-#: taskapp/templates/taskapp/view.html:34
-msgid "Not assigned"
-msgstr "<Не назначено>"
+#: taskapp/templates/taskapp/view.html:42
+msgid "Attachment"
+msgstr "Приложение"
#: taskapp/views.py:82
msgid "You cannot delete task that assigned to you"
@@ -334,12 +358,8 @@ msgstr "Нужно выбрать абонента"
msgid "Error in the form fields"
msgstr "Ошибка в полях формы"
-#: taskapp/views.py:151
-msgid "User does not exist"
-msgstr "Абонент не найден"
-
-msgid "Active tasks"
-msgstr "Активные задачи"
+msgid "Failed tasks"
+msgstr "Проваленные задачи"
msgid "All my tasks"
msgstr "Все мои задачи"
@@ -358,9 +378,3 @@ msgstr "Задачи, которые необходимо выполнить"
msgid "locality %s. Task type - %s. "
msgstr "с. %s. Тип задачи - %s. "
-
-msgid "Name"
-msgstr "ФИО"
-
-msgid "Address"
-msgstr "Адрес"
diff --git a/taskapp/migrations/0015_auto_20170816_1109.py b/taskapp/migrations/0015_auto_20170816_1109.py
new file mode 100644
index 0000000..9755533
--- /dev/null
+++ b/taskapp/migrations/0015_auto_20170816_1109.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9 on 2017-08-16 11:09
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('taskapp', '0014_auto_20170416_1029'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='changelog',
+ name='act_type',
+ field=models.CharField(choices=[('e', 'Изменение задачи'), ('c', 'Создание задачи'), ('d', 'Удаление задачи'), ('f', 'Завершение задачи'), ('b', 'Задача провалена')], max_length=1),
+ ),
+ migrations.AlterField(
+ model_name='task',
+ name='state',
+ field=models.CharField(choices=[('S', 'Новая'), ('C', 'Провалена'), ('F', 'Выполнена')], default='S', max_length=1),
+ ),
+ ]
diff --git a/taskapp/models.py b/taskapp/models.py
index e484e04..37e30f6 100644
--- a/taskapp/models.py
+++ b/taskapp/models.py
@@ -6,8 +6,7 @@ from django.conf import settings
from django.utils import timezone
from django.utils.translation import ugettext as _
from abonapp.models import Abon
-from .handle import handle as task_handle, TaskException
-from mydefs import MultipleException
+from .handle import handle as task_handle
TASK_PRIORITIES = (
@@ -18,7 +17,7 @@ TASK_PRIORITIES = (
TASK_STATES = (
('S', _('New')),
- ('C', _('In fulfilling')),
+ ('C', _('Confused')),
('F', _('Completed'))
)
@@ -44,7 +43,7 @@ class ChangeLog(models.Model):
('c', _('Create task')),
('d', _('Delete task')),
('f', _('Completing tasks')),
- ('b', _('The task started'))
+ ('b', _('The task failed'))
)
act_type = models.CharField(max_length=1, choices=ACT_CHOICES)
when = models.DateTimeField(auto_now_add=True)
@@ -89,8 +88,8 @@ class Task(models.Model):
)
self.save(update_fields=['state', 'out_date'])
- def begin(self, current_user):
- self.state = 'C' # Начата
+ def do_fail(self, current_user):
+ self.state = 'C' # Провалена
ChangeLog.objects.create(
task=self,
act_type='b',
@@ -121,17 +120,10 @@ def task_handler(sender, instance, **kwargs):
act_type='e',
who=instance.author
)
- errors = []
- for recipient in instance.recipients.all():
- try:
- task_handle(
- instance, instance.author,
- recipient, group
- )
- except TaskException as e:
- errors.append(e)
- if len(errors) > 0:
- raise MultipleException(errors)
+ task_handle(
+ instance, instance.author,
+ instance.recipients.all(), group
+ )
#def task_delete(sender, instance, **kwargs):
diff --git a/taskapp/templates/taskapp/add_edit_task.html b/taskapp/templates/taskapp/add_edit_task.html
index d587491..de14dfa 100644
--- a/taskapp/templates/taskapp/add_edit_task.html
+++ b/taskapp/templates/taskapp/add_edit_task.html
@@ -58,15 +58,15 @@