From b418b0c81220cc6c59de2e9bc0c0bfbfde4d9cc3 Mon Sep 17 00:00:00 2001 From: bashmak Date: Mon, 26 Feb 2018 13:40:42 +0300 Subject: [PATCH 01/15] Fix ordering bug --- abonapp/templates/abonapp/peoples.html | 17 +++++++++-------- abonapp/views.py | 16 ++++++++++------ djing/global_base_views.py | 2 +- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/abonapp/templates/abonapp/peoples.html b/abonapp/templates/abonapp/peoples.html index bd3a348..0570a25 100644 --- a/abonapp/templates/abonapp/peoples.html +++ b/abonapp/templates/abonapp/peoples.html @@ -63,18 +63,19 @@ - {% with can_ch_trf=perms.tariff_app.change_tariff can_del_trf=perms.abonapp.delete_abon %} + {% with can_ch_trf=perms.tariff_app.change_tariff can_del_abon=perms.abonapp.delete_abon %} {% for human in peoples %} {% if human.is_active %} - {% else %} + {% else %} {% endif %} {% if human.stat_cache and human.stat_cache.is_online %} {% else %} - {% endif %} + {% endif %} + {{ human.username }} @@ -93,11 +94,11 @@ {{ human.house|default:'-' }} {{ human.telephone }} - {% if human.active_tariff %} - {% if perms.tariff_app.change_tariff %} - {{ human.active_tariff.tariff.title }} + {% if human.current_tariff %} + {% if can_ch_trf %} + {{ human.current_tariff.tariff.title }} {% else %} - {{ human.active_tariff.tariff.title }} + {{ human.current_tariff.tariff.title }} {% endif %} {% else %}——— {% endif %} @@ -108,7 +109,7 @@ {% endfor %} - {% if can_del_trf %} + {% if can_del_abon %} diff --git a/abonapp/views.py b/abonapp/views.py index 30abee0..215f3cd 100644 --- a/abonapp/views.py +++ b/abonapp/views.py @@ -6,7 +6,7 @@ from django.db import IntegrityError, ProgrammingError, transaction from django.db.models import Count, Q, signals from django.shortcuts import render, redirect, get_object_or_404, resolve_url from django.contrib.auth.decorators import login_required -from django.http import HttpResponse, HttpResponseBadRequest +from django.http import HttpResponse, Http404 from django.contrib import messages from django.utils.translation import gettext_lazy as _ from django.utils.decorators import method_decorator @@ -33,7 +33,7 @@ PAGINATION_ITEMS_PER_PAGE = getattr(settings, 'PAGINATION_ITEMS_PER_PAGE', 10) @method_decorator([login_required, mydefs.only_admins], name='dispatch') -class BaseAbonListView(ListView, OrderingMixin): +class BaseAbonListView(OrderingMixin, ListView): paginate_by = PAGINATION_ITEMS_PER_PAGE http_method_names = ['get'] @@ -45,7 +45,7 @@ class PeoplesListView(BaseAbonListView): def get_queryset(self): street_id = mydefs.safe_int(self.request.GET.get('street')) gid = mydefs.safe_int(self.kwargs.get('gid')) - peoples_list = models.Abon.objects.select_related('group', 'street') + peoples_list = models.Abon.objects.all() if street_id > 0: peoples_list = peoples_list.filter(group__pk=gid, street=street_id) else: @@ -60,13 +60,17 @@ class PeoplesListView(BaseAbonListView): pass except mydefs.LogicError as e: messages.warning(self.request, e) - - return peoples_list + ordering = self.get_ordering() + if ordering: + if isinstance(ordering, str): + ordering = (ordering,) + peoples_list = peoples_list.order_by(*ordering) + return peoples_list.select_related('group', 'street', 'current_tariff') def get_context_data(self, **kwargs): gid = mydefs.safe_int(self.kwargs.get('gid')) if gid == 0: - return HttpResponseBadRequest('group id is broken') + return Http404('group id is broken') abon_group = get_object_or_404(models.AbonGroup, pk=gid) if not self.request.user.has_perm('abonapp.can_view_abongroup', abon_group): raise PermissionDenied diff --git a/djing/global_base_views.py b/djing/global_base_views.py index 5eeb350..79f0dcb 100644 --- a/djing/global_base_views.py +++ b/djing/global_base_views.py @@ -87,4 +87,4 @@ class OrderingMixin(object): if direction == 'down': dfx = '-' if order_by: - return ["%s%s" % (dfx, order_by)] + return "%s%s" % (dfx, order_by) From 831f83a63d254cf6abb58891cb1fc907a5e95d6c Mon Sep 17 00:00:00 2001 From: Dmitry Novikov Date: Mon, 26 Feb 2018 15:53:56 +0300 Subject: [PATCH 02/15] fix statistics display --- abonapp/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/abonapp/views.py b/abonapp/views.py index 215f3cd..b0125b3 100644 --- a/abonapp/views.py +++ b/abonapp/views.py @@ -45,7 +45,7 @@ class PeoplesListView(BaseAbonListView): def get_queryset(self): street_id = mydefs.safe_int(self.request.GET.get('street')) gid = mydefs.safe_int(self.kwargs.get('gid')) - peoples_list = models.Abon.objects.all() + peoples_list = models.Abon.objects.all().select_related('group', 'street', 'current_tariff') if street_id > 0: peoples_list = peoples_list.filter(group__pk=gid, street=street_id) else: @@ -65,7 +65,7 @@ class PeoplesListView(BaseAbonListView): if isinstance(ordering, str): ordering = (ordering,) peoples_list = peoples_list.order_by(*ordering) - return peoples_list.select_related('group', 'street', 'current_tariff') + return peoples_list def get_context_data(self, **kwargs): gid = mydefs.safe_int(self.kwargs.get('gid')) From 439e8676fdab38012ed1b5b90f03a6fda24bb6bf Mon Sep 17 00:00:00 2001 From: Dmitry Novikov Date: Tue, 27 Feb 2018 17:29:03 +0300 Subject: [PATCH 03/15] Add comment count to task list --- taskapp/locale/ru/LC_MESSAGES/django.po | 4 ++++ taskapp/templates/taskapp/tasklist.html | 3 ++- taskapp/templates/taskapp/tasklist_all.html | 3 ++- taskapp/views.py | 5 ++++- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/taskapp/locale/ru/LC_MESSAGES/django.po b/taskapp/locale/ru/LC_MESSAGES/django.po index 23dd5c5..acf2fd3 100644 --- a/taskapp/locale/ru/LC_MESSAGES/django.po +++ b/taskapp/locale/ru/LC_MESSAGES/django.po @@ -456,3 +456,7 @@ msgstr "Исполнители" msgid "Author does not specified" msgstr "Автор не указан" + +msgid "Name and comment count" +msgstr "Имя и количество комментов" + diff --git a/taskapp/templates/taskapp/tasklist.html b/taskapp/templates/taskapp/tasklist.html index 60b20e7..4171d95 100644 --- a/taskapp/templates/taskapp/tasklist.html +++ b/taskapp/templates/taskapp/tasklist.html @@ -7,7 +7,7 @@ - + @@ -40,6 +40,7 @@ {{ task.abon.get_full_name }} + {% if task.comment_count > 0 %}({{ task.comment_count }}){% endif %}
{% trans 'Name' %}{% trans 'Name and comment count' %} {% trans 'Address' %} {% trans 'The nature of the damage' %} {% trans 'Description' %} {{ task.abon.group.title }}, {{ task.abon.street|default:'' }} {{ task.abon.house|default:'' }} diff --git a/taskapp/templates/taskapp/tasklist_all.html b/taskapp/templates/taskapp/tasklist_all.html index 6e6b34b..7b9d5f8 100644 --- a/taskapp/templates/taskapp/tasklist_all.html +++ b/taskapp/templates/taskapp/tasklist_all.html @@ -19,7 +19,7 @@ - + @@ -53,6 +53,7 @@ {{ task.abon.get_full_name }} + {% if task.comment_count > 0 %}({{ task.comment_count }}){% endif %} diff --git a/taskapp/views.py b/taskapp/views.py index 68d3db0..ea1eebb 100644 --- a/taskapp/views.py +++ b/taskapp/views.py @@ -3,6 +3,7 @@ from json import dumps from django.contrib.auth.decorators import login_required from django.core.exceptions import PermissionDenied from django.http import HttpResponse +from django.db.models import Count from django.shortcuts import redirect, get_object_or_404, resolve_url from django.contrib import messages from django.utils.decorators import method_decorator @@ -37,6 +38,7 @@ class NewTasksView(BaseTaskListView): def get_queryset(self): return Task.objects.filter(recipients=self.request.user, state='S') \ + .annotate(comment_count=Count('extracomment')) \ .select_related('abon', 'abon__street', 'abon__group', 'author') @@ -85,7 +87,8 @@ class AllTasksListView(BaseTaskListView): context_object_name = 'tasks' def get_queryset(self): - return Task.objects.select_related('abon', 'abon__street', 'abon__group', 'author') + return Task.objects.annotate(comment_count=Count('extracomment')) \ + .select_related('abon', 'abon__street', 'abon__group', 'author') @login_required From 1713beb93ec50a64fecdd5cef277fac280bd7b66 Mon Sep 17 00:00:00 2001 From: bashmak Date: Tue, 27 Feb 2018 18:52:07 +0300 Subject: [PATCH 04/15] Fix 404 error page when data is empty on filtering page and pagination --- abonapp/views.py | 5 ++-- djing/global_base_views.py | 57 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 57 insertions(+), 5 deletions(-) diff --git a/abonapp/views.py b/abonapp/views.py index 215f3cd..812c499 100644 --- a/abonapp/views.py +++ b/abonapp/views.py @@ -26,14 +26,13 @@ from dialing_app.models import AsteriskCDR from statistics.models import getModel from guardian.shortcuts import get_objects_for_user, assign_perm from guardian.decorators import permission_required_or_403 as permission_required -from djing.global_base_views import OrderingMixin - +from djing.global_base_views import OrderingMixin, BaseListWithFiltering PAGINATION_ITEMS_PER_PAGE = getattr(settings, 'PAGINATION_ITEMS_PER_PAGE', 10) @method_decorator([login_required, mydefs.only_admins], name='dispatch') -class BaseAbonListView(OrderingMixin, ListView): +class BaseAbonListView(OrderingMixin, BaseListWithFiltering): paginate_by = PAGINATION_ITEMS_PER_PAGE http_method_names = ['get'] diff --git a/djing/global_base_views.py b/djing/global_base_views.py index 79f0dcb..9dd83fe 100644 --- a/djing/global_base_views.py +++ b/djing/global_base_views.py @@ -1,14 +1,26 @@ from hashlib import sha256 from django.views.generic.base import View -from django.http.response import HttpResponseForbidden +from django.http.response import HttpResponseForbidden, Http404, HttpResponseRedirect +from django.utils.translation import gettext_lazy as _ from django.conf import settings +from django.views.generic import ListView from netaddr import IPNetwork, IPAddress - +from django.core.paginator import InvalidPage, EmptyPage API_AUTH_SECRET = getattr(settings, 'API_AUTH_SECRET') API_AUTH_SUBNET = getattr(settings, 'API_AUTH_SUBNET') +class RedirectWhenError(Exception): + def __init__(self, url, failed_message=None): + self.url = url + if failed_message is not None: + self.message = failed_message + + def __str__(self): + return self.message or '' + + class HashAuthView(View): @staticmethod @@ -88,3 +100,44 @@ class OrderingMixin(object): dfx = '-' if order_by: return "%s%s" % (dfx, order_by) + + +class BaseListWithFiltering(ListView): + """ + When queryset contains filter and pagination than data may be missing, + and original code is raising 404 error. We want to redirect without pagination. + """ + + def paginate_queryset(self, queryset, page_size): + paginator = self.get_paginator( + queryset, page_size, orphans=self.get_paginate_orphans(), + allow_empty_first_page=self.get_allow_empty()) + page_kwarg = self.page_kwarg + page = self.kwargs.get(page_kwarg) or self.request.GET.get(page_kwarg) or 1 + try: + page_number = int(page) + except ValueError: + if page == 'last': + page_number = paginator.num_pages + else: + raise Http404(_("Page is not 'last', nor can it be converted to an int.")) + try: + page = paginator.page(page_number) + return paginator, page, page.object_list, page.has_other_pages() + except EmptyPage: + # remove pagination from url + url = self.request.GET.copy() + del url[self.page_kwarg] + raise RedirectWhenError("%s?%s" % (self.request.path, url.urlencode()), + _('Filter does not contains data, filter without pagination')) + except InvalidPage as e: + raise Http404(_('Invalid page (%(page_number)s): %(message)s') % { + 'page_number': page_number, + 'message': str(e) + }) + + def get(self, request, *args, **kwargs): + try: + return super(BaseListWithFiltering, self).get(request, *args, **kwargs) + except RedirectWhenError as e: + return HttpResponseRedirect(e.url) From 17ed2fc7535f7b2612351c59ed02a091609bdd25 Mon Sep 17 00:00:00 2001 From: www-data Date: Thu, 1 Mar 2018 14:48:00 +0300 Subject: [PATCH 05/15] fix --- devapp/onu_register.sh | 2 +- djing/settings.py | 2 +- queue_mngr.py | 32 +++++++++++++++++------------ systemd_units/djing.service | 2 +- systemd_units/djing_queue.service | 2 +- systemd_units/djing_telebot.service | 2 +- 6 files changed, 24 insertions(+), 18 deletions(-) diff --git a/devapp/onu_register.sh b/devapp/onu_register.sh index c835550..fb64e5a 100755 --- a/devapp/onu_register.sh +++ b/devapp/onu_register.sh @@ -29,6 +29,6 @@ if grep "${MAC}" "${DHCP_PATH}/${PART_CODE}.conf" > /dev/null; then else # add new mac echo "subclass \"${PART_CODE}\" \"${MAC}\";" >> "${DHCP_PATH}/${PART_CODE}.conf" - /usr/bin/sudo /usr/bin/systemctl restart dhcpd.service + sudo systemctl restart isc-dhcp-server.service fi diff --git a/djing/settings.py b/djing/settings.py index 3c836a1..cdeb813 100644 --- a/djing/settings.py +++ b/djing/settings.py @@ -117,7 +117,7 @@ AUTH_PASSWORD_VALIDATORS = [ }, ] -SESSION_ENGINE = 'django.contrib.sessions.backends.file' +SESSION_ENGINE = 'django.contrib.sessions.backends.db' SESSION_COOKIE_HTTPONLY = True diff --git a/queue_mngr.py b/queue_mngr.py index 25d3e47..c066eff 100755 --- a/queue_mngr.py +++ b/queue_mngr.py @@ -4,6 +4,8 @@ from pickle import loads from pid.decorator import pidfile import socket import django +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djing.settings") +from mydefs import LogicError ''' @@ -18,19 +20,23 @@ obj = { def on_new_data(client_sock, ip): - data = client_sock.recv(16384) - data = loads(data) - action = data['cmd'] - if action == 'commit': - dhcp_commit( - data['client_ip'], data['client_mac'], - data['switch_mac'], data['switch_port'] - ) - elif action == 'expiry': - dhcp_expiry(data['client_ip']) - elif action == 'release': - dhcp_release(data['client_ip']) - client_sock.close() + try: + data = client_sock.recv(16384) + data = loads(data) + action = data['cmd'] + if action == 'commit': + dhcp_commit( + data['client_ip'], data['client_mac'], + data['switch_mac'], data['switch_port'] + ) + elif action == 'expiry': + dhcp_expiry(data['client_ip']) + elif action == 'release': + dhcp_release(data['client_ip']) + except LogicError as e: + print('LogicError', e) + finally: + client_sock.close() @pidfile(pidname='queue_mngr.py.pid', piddir='/run') diff --git a/systemd_units/djing.service b/systemd_units/djing.service index 2e4a286..15216ee 100644 --- a/systemd_units/djing.service +++ b/systemd_units/djing.service @@ -3,7 +3,7 @@ Description=A job for djing [Service] Type=simple -ExecStart=/usr/bin/python3 cron.py > /dev/null +ExecStart=/usr/bin/python3 cron.py WorkingDirectory=/srv/http/djing User=http Group=http diff --git a/systemd_units/djing_queue.service b/systemd_units/djing_queue.service index 830a585..e2800c2 100644 --- a/systemd_units/djing_queue.service +++ b/systemd_units/djing_queue.service @@ -6,7 +6,7 @@ Type=simple ExecStart=/usr/bin/python3 ./queue_mngr.py > /dev/null PIDFile=/run/queue_mngr.py.pid WorkingDirectory=/var/www/djing -TimeoutSec=15 +TimeoutSec=30 Restart=always User=http Group=http diff --git a/systemd_units/djing_telebot.service b/systemd_units/djing_telebot.service index 2d909a3..2afa575 100644 --- a/systemd_units/djing_telebot.service +++ b/systemd_units/djing_telebot.service @@ -3,7 +3,7 @@ Description=Djing telegram bot [Service] Type=simple -ExecStart=/usr/bin/python3 ./telebot.py > /dev/null +ExecStart=/usr/bin/python3 ./telebot.py PIDFile=/run/djing_telebot.pid WorkingDirectory=/var/www/djing TimeoutSec=9 From fb5ae9af6a9bcd945bd3e54799686385b2a5c3aa Mon Sep 17 00:00:00 2001 From: bashmak Date: Thu, 1 Mar 2018 14:55:10 +0300 Subject: [PATCH 06/15] Fix dlink view error --- devapp/base_intr.py | 19 +++++++------ devapp/dev_types.py | 28 +++++++++++-------- .../devapp/custom_dev_page/ports.html | 8 +++--- devapp/views.py | 3 +- 4 files changed, 33 insertions(+), 25 deletions(-) diff --git a/devapp/base_intr.py b/devapp/base_intr.py index 715dcc9..b5576fb 100644 --- a/devapp/base_intr.py +++ b/devapp/base_intr.py @@ -1,8 +1,11 @@ -# -*- coding: utf-8 -*- from abc import ABCMeta, abstractmethod from easysnmp import Session +class DeviceImplementationError(Exception): + pass + + class DevBase(object, metaclass=ABCMeta): def __init__(self, dev_instance=None): @@ -10,19 +13,19 @@ class DevBase(object, metaclass=ABCMeta): @staticmethod def description(): - """Возвращает текстовое описание""" + pass @abstractmethod def reboot(self): - """Перезагружает устройство""" + pass @abstractmethod def get_ports(self): - """Получаем инфу о портах""" + pass @abstractmethod def get_device_name(self): - """Получаем имя устройства по snmp""" + """Return device name by snmp""" @abstractmethod def uptime(self): @@ -30,17 +33,17 @@ class DevBase(object, metaclass=ABCMeta): @abstractmethod def get_template_name(self): - """Получаем путь к html шаблону отображения устройства""" + """Return path to html template for device""" @staticmethod @abstractmethod def has_attachable_to_subscriber(): - """Можно-ли подключать устройство к абоненту""" + """Can connect device to subscriber""" @staticmethod @abstractmethod def is_use_device_port(): - """True если при авторизации по opt82 используется порт""" + """True if used device port while opt82 authorization""" class BasePort(object, metaclass=ABCMeta): diff --git a/devapp/dev_types.py b/devapp/dev_types.py index 5db0309..244ebd8 100644 --- a/devapp/dev_types.py +++ b/devapp/dev_types.py @@ -3,7 +3,7 @@ from django.utils.translation import gettext_lazy as _ from mydefs import RuTimedelta, safe_int from datetime import timedelta from easysnmp import EasySNMPTimeoutError -from .base_intr import DevBase, SNMPBaseWorker, BasePort +from .base_intr import DevBase, SNMPBaseWorker, BasePort, DeviceImplementationError class DLinkPort(BasePort): @@ -41,21 +41,25 @@ class DLinkDevice(DevBase, SNMPBaseWorker): return self.get_item('.1.3.6.1.4.1.2021.8.1.101.1') def get_ports(self): + interfaces_count = safe_int(self.get_item('.1.3.6.1.2.1.2.1.0')) nams = list(self.get_list('.1.3.6.1.4.1.171.10.134.2.1.1.100.2.1.3')) stats = list(self.get_list('.1.3.6.1.2.1.2.2.1.7')) macs = list(self.get_list('.1.3.6.1.2.1.2.2.1.6')) - speeds = self.get_list('.1.3.6.1.2.1.31.1.1.1.15') + speeds = list(self.get_list('.1.3.6.1.2.1.2.2.1.5')) res = [] - for n, speed in enumerate(speeds): - status = True if int(stats[n]) == 1 else False - res.append(DLinkPort( - n+1, - nams[n] if len(nams) > 0 else _('does not fetch the name'), - status, - macs[n] if len(macs) > 0 else _('does not fetch the mac'), - int(speed or 0), - self)) - return res + try: + for n in range(interfaces_count): + status = True if int(stats[n]) == 1 else False + res.append(DLinkPort( + n+1, + nams[n] if len(nams) > 0 else _('does not fetch the name'), + status, + macs[n] if len(macs) > 0 else _('does not fetch the mac'), + int(speeds[n]) if len(speeds) > 0 else 0, + self)) + return res + except IndexError: + raise DeviceImplementationError('Dlink port index error') def get_device_name(self): return self.get_item('.1.3.6.1.2.1.1.1.0') diff --git a/devapp/templates/devapp/custom_dev_page/ports.html b/devapp/templates/devapp/custom_dev_page/ports.html index 1651200..57bd85d 100644 --- a/devapp/templates/devapp/custom_dev_page/ports.html +++ b/devapp/templates/devapp/custom_dev_page/ports.html @@ -18,16 +18,16 @@ {% for port in ports %} {% if port.st %} - {% if port.sp == 10 %} + {% if port.sp == 10000000 %}
10 mbps - {% elif port.sp == 100 %} + {% elif port.sp == 100000000 %}
100 mbps - {% elif port.sp == 1000 %} + {% elif port.sp == 1000000000 %}
1 gbps - {% elif port.sp == 10000 %} + {% elif port.sp == 10000000000 %}
10 gbps {% else %} diff --git a/devapp/views.py b/devapp/views.py index f932d9d..9abc4bc 100644 --- a/devapp/views.py +++ b/devapp/views.py @@ -13,6 +13,7 @@ from django.utils.translation import gettext_lazy as _, gettext from easysnmp import EasySNMPTimeoutError, EasySNMPError from django.views.generic import ListView, DetailView +from devapp.base_intr import DeviceImplementationError from mydefs import res_success, res_error, only_admins, ping, ip_addr_regex from abonapp.models import AbonGroup, Abon from django.conf import settings @@ -371,7 +372,7 @@ def devview(request, device_id): }) except EasySNMPError: messages.error(request, _('SNMP error on device')) - except DeviceDBException as e: + except (DeviceDBException, DeviceImplementationError) as e: messages.error(request, e) return render(request, 'devapp/custom_dev_page/' + template_name, { 'dev': dev From 38017de5ae5a2ca625ef5111aea3e397a54bb512 Mon Sep 17 00:00:00 2001 From: bashmak Date: Thu, 1 Mar 2018 18:17:45 +0300 Subject: [PATCH 07/15] make migration to version 0.2 and translate comment --- chatbot/models.py | 2 +- migrate_to_0.2.py | 238 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 239 insertions(+), 1 deletion(-) create mode 100755 migrate_to_0.2.py diff --git a/chatbot/models.py b/chatbot/models.py index fffe6f1..b10f2bd 100644 --- a/chatbot/models.py +++ b/chatbot/models.py @@ -57,7 +57,7 @@ class MessageQueue(models.Model): ('r', 'Read') ) status = models.CharField(_('Status of message'), max_length=1, choices=STATUSES, default='n') - # tag каждое приложение ставит своим чтоб делить сообщения между этими приложениями + # tag: each application puts its own to separate messages between these applications tag = models.CharField(_('App tag'), max_length=6, default='none') objects = MessageQueueManager() diff --git a/migrate_to_0.2.py b/migrate_to_0.2.py new file mode 100755 index 0000000..76d553a --- /dev/null +++ b/migrate_to_0.2.py @@ -0,0 +1,238 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +import os +import sys +import shutil +from json import dump +import django + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djing.settings") +from django.db.models import fields as django_fields + + +def get_fixture_from_unchanget_model(model_name: str, model_class): + """ + Создаёт фикстуру если модели между версиями не изменились + :param model_name: str 'app_label.model_name' + :param model_class: Model модель для которой надо сделать фикстуру + :return: список словарей + """ + print(model_name) + + def get_fields(obj): + fields = dict() + for field in obj._meta.get_fields(): + if isinstance(field, django_fields.reverse_related.ManyToOneRel) or \ + isinstance(field, django_fields.reverse_related.ManyToManyRel): + continue + field_val = getattr(obj, field.name) + if field_val is None: + continue + if isinstance(field, django_fields.related.ManyToManyField): + fields[field.name] = [f.pk for f in field_val.all()] + elif isinstance(field, django_fields.related.ForeignKey): + fields[field.name] = field_val.pk + elif isinstance(field, django_fields.FloatField): + fields[field.name] = float(field_val) + elif isinstance(field, django_fields.DateTimeField): + fields[field.name] = str(field_val) + elif isinstance(field, django_fields.AutoField): + continue + else: + fields[field.name] = field_val + return fields + + res = [{ + 'model': model_name, + 'pk': obj.pk, + 'fields': get_fields(obj) + } for obj in model_class.objects.all()] + return res + + +def dump_abonapp(): + from abonapp import models + print('group_app.group') + res = [{ + 'model': 'group_app.group', + 'pk': abon_group.pk, + 'fields': { + 'title': abon_group.title + } + } for abon_group in models.AbonGroup.objects.all()] + + # res += get_fixture_from_unchanget_model('abonapp.abonlog', models.AbonLog) + + res += get_fixture_from_unchanget_model('abonapp.abontariff', models.AbonTariff) + + res += get_fixture_from_unchanget_model('abonapp.abonstreet', models.AbonStreet) + + res += get_fixture_from_unchanget_model('abonapp.extrafieldsmodel', models.ExtraFieldsModel) + + res += get_fixture_from_unchanget_model('abonapp.abon', models.Abon) + + res += get_fixture_from_unchanget_model('abonapp.passportinfo', models.PassportInfo) + + res += get_fixture_from_unchanget_model('abonapp.invoiceforpayment', models.InvoiceForPayment) + + res += get_fixture_from_unchanget_model('abonapp.alltimepaylog', models.AllTimePayLog) + + res += get_fixture_from_unchanget_model('abonapp.abonrawpassword', models.AbonRawPassword) + + res += get_fixture_from_unchanget_model('abonapp.additionaltelephone', models.AdditionalTelephone) + + res += get_fixture_from_unchanget_model('abonapp.periodicpayforid', models.PeriodicPayForId) + + return res + + +def dump_tariffs(): + from tariff_app import models + res = get_fixture_from_unchanget_model('tariff_app.tariff', models.Tariff) + + res += get_fixture_from_unchanget_model('tariff_app.periodicpay', models.PeriodicPay) + + return res + + +def dump_devs(): + from devapp import models + print('devapp.device') + res = [{ + 'model': 'devapp.device', + 'pk': dv.pk, + 'fields': { + 'ip_address': dv.ip_address, + 'mac_addr': dv.mac_addr, + 'devtype': dv.devtype, + 'man_passw': dv.man_passw, + 'group': dv.user_group.pk if dv.user_group else 0, + 'parent_dev': dv.parent_dev.pk if dv.parent_dev else 0 + } + } for dv in models.Device.objects.all()] + + res += get_fixture_from_unchanget_model('devapp.port', models.Port) + return res + + +def dump_accounts(): + from accounts_app import models + from abonapp.models import AbonGroup + + def get_responsibility_groups(account): + responsibility_groups = AbonGroup.objects.filter(profiles__in=[account]) + ids = [ag.pk for ag in responsibility_groups] + return ids + + print('accounts_app.userprofile') + res = [{ + 'model': 'accounts_app.userprofile', + 'pk': up.pk, + 'fields': { + 'username': up.username, + 'fio': up.fio, + 'birth_day': up.birth_day, + 'is_active': up.is_active, + 'is_admin': up.is_admin, + 'telephone': up.telephone, + 'avatar': up.avatar.pk if up.avatar else 0, + 'email': up.email, + 'responsibility_groups': get_responsibility_groups(up) + } + } for up in models.UserProfile.objects.filter(is_admin=True)] + + return res + + +def dump_photos(): + from photo_app.models import Photo + res = get_fixture_from_unchanget_model('photo_app.photo', Photo) + return res + + +def dump_chatbot(): + from chatbot import models + res = get_fixture_from_unchanget_model('chatbot.telegrambot', models.TelegramBot) + res += get_fixture_from_unchanget_model('chatbot.messagehistory', models.MessageHistory) + res += get_fixture_from_unchanget_model('chatbot.messagequeue', models.MessageQueue) + return res + + +def dump_map(): + from mapapp import models + res = get_fixture_from_unchanget_model('mapapp.dot', models.Dot) + return res + + +def dump_messages(): + from msg_app import models + res = get_fixture_from_unchanget_model('msg_app.messagestatus', models.MessageStatus) + res += get_fixture_from_unchanget_model('msg_app.message', models.Message) + res += get_fixture_from_unchanget_model('msg_app.conversationmembership', models.ConversationMembership) + res += get_fixture_from_unchanget_model('msg_app.conversation', models.Conversation) + return res + + +def dump_task_app(): + from taskapp import models + res = get_fixture_from_unchanget_model('taskapp.changelog', models.ChangeLog) + res += get_fixture_from_unchanget_model('taskapp.task', models.Task) + res += get_fixture_from_unchanget_model('taskapp.ExtraComment', models.ExtraComment) + return res + + +def make_migration(): + from datetime import datetime + from datetime import date + + def my_date_converter(o): + if isinstance(o, datetime) or isinstance(o, date): + return "%s" % o + + def appdump(path, func): + fname = os.path.join(*path) + path_dir = os.path.join(*path[:-1]) + if not os.path.isdir(path_dir): + os.mkdir(path_dir) + with open(fname, 'w') as f: + dump(func(), f, default=my_date_converter, ensure_ascii=False) + + django.setup() + appdump(['fixtures', 'tariff_app', 'tariffs_fixture.json'], dump_tariffs) + appdump(['fixtures', 'photo_app', 'photos_fixture.json'], dump_photos) + appdump(['fixtures', 'devapp', 'devs_fixture.json'], dump_devs) + appdump(['fixtures', 'abonapp', 'abon_fixture.json'], dump_abonapp) + appdump(['fixtures', 'accounts_app', 'accounts_fixture.json'], dump_accounts) + + appdump(['fixtures', 'chatbot', 'chatbot_fixture.json'], dump_chatbot) + appdump(['fixtures', 'mapapp', 'map_fixture.json'], dump_map) + appdump(['fixtures', 'msg_app', 'mesages_fixture.json'], dump_messages) + appdump(['fixtures', 'taskapp', 'task_fixture.json'], dump_task_app) + + +def move_to_fixtures_dirs(): + fixdir = 'fixtures' + for dr in os.listdir(fixdir): + fixture_dir = os.path.join(fixdir, dr) + fixture_file = os.listdir( fixture_dir )[0] + from_file = os.path.join(fixture_dir, fixture_file) + dst_dir = os.path.join(dr, fixdir) + to_file = os.path.join(dst_dir, fixture_file) + if not os.path.isdir( dst_dir ): + os.mkdir(dst_dir) + shutil.copyfile( from_file, to_file ) + print('cp %s -> %s' % (from_file, to_file)) + + +if __name__ == '__main__': + if len(sys.argv) < 2: + print('Usage: ./migrate_to_0.2.py [makedump OR applydump]') + exit(1) + choice = sys.argv[1] + if choice == 'applydump': + move_to_fixtures_dirs() + print('And now apply created fixtures by ./manage.py loaddata') + elif choice == 'makedump': + make_migration() + else: + print('Unexpected choice') From 291d9d6a0de2299d1a26a678fdaee6f9b350f1d4 Mon Sep 17 00:00:00 2001 From: bashmak Date: Tue, 6 Mar 2018 16:30:34 +0300 Subject: [PATCH 08/15] Fix when btn-modal retrn 302 and not redirected --- devapp/views.py | 23 ++++++++---- djing/global_base_views.py | 26 +++++++++---- migrate_to_0.2.py | 67 ++++++++++++++++++++++++++-------- static/js/my.js | 15 ++++++-- systemd_units/webdav_backup.py | 23 ++++++++++-- 5 files changed, 115 insertions(+), 39 deletions(-) diff --git a/devapp/views.py b/devapp/views.py index 9abc4bc..7d13476 100644 --- a/devapp/views.py +++ b/devapp/views.py @@ -22,12 +22,11 @@ from guardian.shortcuts import get_objects_for_user from chatbot.telebot import send_notify from chatbot.models import ChatException from jsonview.decorators import json_view -from djing.global_base_views import HashAuthView, AllowedSubnetMixin, OrderingMixin +from djing import global_base_views from .models import Device, Port, DeviceDBException, DeviceMonitoringException from .forms import DeviceForm, PortForm from mydefs import safe_int - PAGINATION_ITEMS_PER_PAGE = getattr(settings, 'PAGINATION_ITEMS_PER_PAGE', 10) @@ -37,7 +36,7 @@ class BaseDeviceListView(ListView): @method_decorator([login_required, only_admins], name='dispatch') -class DevicesListView(BaseDeviceListView, OrderingMixin): +class DevicesListView(BaseDeviceListView, global_base_views.OrderingMixin): context_object_name = 'devices' template_name = 'devapp/devices.html' @@ -64,7 +63,7 @@ class DevicesListView(BaseDeviceListView, OrderingMixin): @method_decorator([login_required, only_admins], name='dispatch') -class DevicesWithoutGroupsListView(BaseDeviceListView, OrderingMixin): +class DevicesWithoutGroupsListView(BaseDeviceListView, global_base_views.OrderingMixin): context_object_name = 'devices' template_name = 'devapp/devices_null_group.html' queryset = Device.objects.filter(user_group=None).only('comment', 'devtype', 'user_group', 'pk', 'ip_address') @@ -181,7 +180,7 @@ def manage_ports(request, device_id): @method_decorator([login_required, only_admins], name='dispatch') -class ShowSubscriberOnPort(DetailView): +class ShowSubscriberOnPort(global_base_views.RedirectWhenErrorMixin, DetailView): template_name = 'devapp/manage_ports/modal_show_subscriber_on_port.html' http_method_names = ['get'] @@ -192,6 +191,14 @@ class ShowSubscriberOnPort(DetailView): obj = Abon.objects.get(device_id=dev_id, dev_port_id=port_id) except Abon.DoesNotExist: raise Http404(gettext('Subscribers on port does not exist')) + except Abon.MultipleObjectsReturned: + errmsg = gettext('More than one subscriber on device port') + # messages.error(self.request, errmsg) + raise global_base_views.RedirectWhenError( + resolve_url('devapp:fix_port_conflict', group_id=self.kwargs.get('group_id'), device_id=dev_id, + port_id=port_id), + errmsg + ) return obj @@ -468,8 +475,8 @@ def fix_onu(request): if parent is not None: manobj = parent.get_manager_object() ports = manobj.get_list_keyval('.1.3.6.1.4.1.3320.101.10.1.1.3') - text = ' ' %\ - (_('Device with mac address %(mac)s does not exist') % {'mac': mac}) + text = ' ' % \ + (_('Device with mac address %(mac)s does not exist') % {'mac': mac}) for srcmac, snmpnum in ports: real_mac = ':'.join(['%x' % ord(i) for i in srcmac]) if mac == real_mac: @@ -502,7 +509,7 @@ def fix_port_conflict(request, group_id, device_id, port_id): }) -class OnDevDown(AllowedSubnetMixin, HashAuthView): +class OnDevDown(global_base_views.AllowedSubnetMixin, global_base_views.HashAuthView): # # Api view for monitoring devices # diff --git a/djing/global_base_views.py b/djing/global_base_views.py index 9dd83fe..51040f2 100644 --- a/djing/global_base_views.py +++ b/djing/global_base_views.py @@ -1,6 +1,7 @@ from hashlib import sha256 +from json import dumps from django.views.generic.base import View -from django.http.response import HttpResponseForbidden, Http404, HttpResponseRedirect +from django.http.response import HttpResponseForbidden, Http404, HttpResponseRedirect, HttpResponse from django.utils.translation import gettext_lazy as _ from django.conf import settings from django.views.generic import ListView @@ -102,7 +103,22 @@ class OrderingMixin(object): return "%s%s" % (dfx, order_by) -class BaseListWithFiltering(ListView): +class RedirectWhenErrorMixin(object): + + def get(self, request, *args, **kwargs): + try: + return super(RedirectWhenErrorMixin, self).get(request, *args, **kwargs) + except RedirectWhenError as e: + if request.is_ajax(): + return HttpResponse(dumps({ + 'url': e.url, + 'text': e.message or '' + })) + else: + return HttpResponseRedirect(e.url) + + +class BaseListWithFiltering(RedirectWhenErrorMixin, ListView): """ When queryset contains filter and pagination than data may be missing, and original code is raising 404 error. We want to redirect without pagination. @@ -135,9 +151,3 @@ class BaseListWithFiltering(ListView): 'page_number': page_number, 'message': str(e) }) - - def get(self, request, *args, **kwargs): - try: - return super(BaseListWithFiltering, self).get(request, *args, **kwargs) - except RedirectWhenError as e: - return HttpResponseRedirect(e.url) diff --git a/migrate_to_0.2.py b/migrate_to_0.2.py index 76d553a..7015628 100755 --- a/migrate_to_0.2.py +++ b/migrate_to_0.2.py @@ -50,7 +50,7 @@ def get_fixture_from_unchanget_model(model_name: str, model_class): return res -def dump_abonapp(): +def dump_groups(): from abonapp import models print('group_app.group') res = [{ @@ -60,16 +60,44 @@ def dump_abonapp(): 'title': abon_group.title } } for abon_group in models.AbonGroup.objects.all()] + return res + +def dump_abonapp(): + from abonapp import models # res += get_fixture_from_unchanget_model('abonapp.abonlog', models.AbonLog) - res += get_fixture_from_unchanget_model('abonapp.abontariff', models.AbonTariff) + res = get_fixture_from_unchanget_model('abonapp.abontariff', models.AbonTariff) res += get_fixture_from_unchanget_model('abonapp.abonstreet', models.AbonStreet) res += get_fixture_from_unchanget_model('abonapp.extrafieldsmodel', models.ExtraFieldsModel) - res += get_fixture_from_unchanget_model('abonapp.abon', models.Abon) + res += [{ + 'model': 'abonapp.abon', + 'pk': abon.pk, + 'fields': { + 'current_tariff': abon.current_tariff.pk if abon.current_tariff else None, + 'group': abon.group.pk if abon.group else None, + 'ballance': abon.ballance, + 'ip_address': abon.ip_address, + 'description': abon.description, + 'street': abon.street.pk if abon.street else None, + 'house': abon.house, + 'extra_fields': [a.pk for a in abon.extra_fields.all()], + 'device': abon.device.pk if abon.device else None, + 'dev_port': abon.dev_port if abon.dev_port else None, + 'is_dynamic_ip': abon.is_dynamic_ip, + 'markers': abon.markers, + + 'username': abon.username, + 'fio': abon.fio, + 'birth_day': abon.birth_day, + 'is_active': abon.is_active, + 'is_admin': abon.is_admin, + 'telephone': abon.telephone + } + } for abon in models.Abon.objects.filter(is_admin=False)] res += get_fixture_from_unchanget_model('abonapp.passportinfo', models.PassportInfo) @@ -106,8 +134,8 @@ def dump_devs(): 'mac_addr': dv.mac_addr, 'devtype': dv.devtype, 'man_passw': dv.man_passw, - 'group': dv.user_group.pk if dv.user_group else 0, - 'parent_dev': dv.parent_dev.pk if dv.parent_dev else 0 + 'group': dv.user_group.pk if dv.user_group else None, + 'parent_dev': dv.parent_dev.pk if dv.parent_dev else None } } for dv in models.Device.objects.all()] @@ -135,7 +163,7 @@ def dump_accounts(): 'is_active': up.is_active, 'is_admin': up.is_admin, 'telephone': up.telephone, - 'avatar': up.avatar.pk if up.avatar else 0, + 'avatar': up.avatar.pk if up.avatar else None, 'email': up.email, 'responsibility_groups': get_responsibility_groups(up) } @@ -146,7 +174,15 @@ def dump_accounts(): def dump_photos(): from photo_app.models import Photo - res = get_fixture_from_unchanget_model('photo_app.photo', Photo) + res = [{ + 'model': 'photo_app.photo', + 'pk': p.pk, + 'fields': { + 'image': "%s" % p.image, + 'wdth': p.wdth, + 'heigt': p.heigt + } + } for p in Photo.objects.all()] return res @@ -198,16 +234,17 @@ def make_migration(): dump(func(), f, default=my_date_converter, ensure_ascii=False) django.setup() - appdump(['fixtures', 'tariff_app', 'tariffs_fixture.json'], dump_tariffs) - appdump(['fixtures', 'photo_app', 'photos_fixture.json'], dump_photos) - appdump(['fixtures', 'devapp', 'devs_fixture.json'], dump_devs) - appdump(['fixtures', 'abonapp', 'abon_fixture.json'], dump_abonapp) + #appdump(['fixtures', 'group_app', 'groups_fixture.json'], dump_groups) + #appdump(['fixtures', 'tariff_app', 'tariffs_fixture.json'], dump_tariffs) + #appdump(['fixtures', 'photo_app', 'photos_fixture.json'], dump_photos) + #appdump(['fixtures', 'devapp', 'devs_fixture.json'], dump_devs) appdump(['fixtures', 'accounts_app', 'accounts_fixture.json'], dump_accounts) + appdump(['fixtures', 'abonapp', 'abon_fixture.json'], dump_abonapp) - appdump(['fixtures', 'chatbot', 'chatbot_fixture.json'], dump_chatbot) - appdump(['fixtures', 'mapapp', 'map_fixture.json'], dump_map) - appdump(['fixtures', 'msg_app', 'mesages_fixture.json'], dump_messages) - appdump(['fixtures', 'taskapp', 'task_fixture.json'], dump_task_app) + #appdump(['fixtures', 'chatbot', 'chatbot_fixture.json'], dump_chatbot) + #appdump(['fixtures', 'mapapp', 'map_fixture.json'], dump_map) + #appdump(['fixtures', 'msg_app', 'mesages_fixture.json'], dump_messages) + #appdump(['fixtures', 'taskapp', 'task_fixture.json'], dump_task_app) def move_to_fixtures_dirs(): diff --git a/static/js/my.js b/static/js/my.js index 78592fa..953f9bf 100644 --- a/static/js/my.js +++ b/static/js/my.js @@ -214,7 +214,7 @@ $(document).ajaxError(function (ev, jqXHR, ajaxSettings, thrownError) { } if(settings.news_url){ - // прверяем новости раз в минуту + // once per minute check news var tiid = setInterval(check_news, settings.check_interval*1000); //Notification.requestPermission(on_ask_perm); @@ -266,13 +266,20 @@ $(document).ready(function () { }); $('.btn-modal').on('click', function(){ - $.get(this.href, function(r){ - show_ModalMyContent(r); + $.get(this.href, function(response){ + console.log(response); + try{ + var r = $.parseJSON(response); + console.log(r.text); + window.location.replace(r.url); + }catch (e){ + show_ModalMyContent(response); + } }); return false; }); - // кнопка посылающая комманду и возвращающая результат выполнения + // button that send command and return response of that $('.btn-cmd').on('click', function(){ var cmd_param = $(this).attr('data-param'); var self = $(this); diff --git a/systemd_units/webdav_backup.py b/systemd_units/webdav_backup.py index 40982e4..967de77 100644 --- a/systemd_units/webdav_backup.py +++ b/systemd_units/webdav_backup.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 -import webdav.client as wc -from webdav.client import WebDavException from sys import argv +from datetime import datetime, timedelta +import webdav.client as wc options = { @@ -10,11 +10,26 @@ options = { 'webdav_password': "YANDEX PASSWORD" } + +def remove_old_files(border_time : datetime, client): + # files that older than border_time will be removed + for file in client.list('ISBackups'): + fdate = datetime.strptime(file, 'djing%Y-%m-%d_%H.%M.%S.sql.gz') + if fdate < border_time: + del_fname = 'ISBackups/%' % file + client.clean( del_fname ) + print("rm %s" % del_fname) + + if __name__ == '__main__': reqfile = argv[1] try: client = wc.Client(options) - client.upload_sync(remote_path="ISBackups/%s" % reqfile, local_path="/var/backups/%s" % reqfile) - except WebDavException as we: + if reqfile == 'rotate': + border_time = datetime.now() - timedelta(month=3) + remove_old_files(border_time, client) + else: + client.upload_sync(remote_path="ISBackups/%s" % reqfile, local_path="/var/backups/%s" % reqfile) + except wc.WebDavException as we: print(we, type(we)) From c83e891760c256958ca1334c944abc34a31d06c8 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 6 Mar 2018 23:34:32 +0000 Subject: [PATCH 09/15] Work is still continue --- migrate_to_0.2.py | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/migrate_to_0.2.py b/migrate_to_0.2.py index 7015628..2d8a7f3 100755 --- a/migrate_to_0.2.py +++ b/migrate_to_0.2.py @@ -73,6 +73,7 @@ def dump_abonapp(): res += get_fixture_from_unchanget_model('abonapp.extrafieldsmodel', models.ExtraFieldsModel) + print('abonapp.abon') res += [{ 'model': 'abonapp.abon', 'pk': abon.pk, @@ -88,14 +89,7 @@ def dump_abonapp(): 'device': abon.device.pk if abon.device else None, 'dev_port': abon.dev_port if abon.dev_port else None, 'is_dynamic_ip': abon.is_dynamic_ip, - 'markers': abon.markers, - - 'username': abon.username, - 'fio': abon.fio, - 'birth_day': abon.birth_day, - 'is_active': abon.is_active, - 'is_admin': abon.is_admin, - 'telephone': abon.telephone + 'markers': abon.markers } } for abon in models.Abon.objects.filter(is_admin=False)] @@ -152,9 +146,9 @@ def dump_accounts(): ids = [ag.pk for ag in responsibility_groups] return ids - print('accounts_app.userprofile') + print('accounts_app.baseaccount') res = [{ - 'model': 'accounts_app.userprofile', + 'model': 'accounts_app.baseaccount', 'pk': up.pk, 'fields': { 'username': up.username, @@ -162,7 +156,15 @@ def dump_accounts(): 'birth_day': up.birth_day, 'is_active': up.is_active, 'is_admin': up.is_admin, - 'telephone': up.telephone, + 'telephone': up.telephone + } + } for up in models.UserProfile.objects.exclude(username='AnonymousUser')] + + print('accounts_app.userprofile') + res += [{ + 'model': 'accounts_app.userprofile', + 'pk': up.pk, + 'fields': { 'avatar': up.avatar.pk if up.avatar else None, 'email': up.email, 'responsibility_groups': get_responsibility_groups(up) @@ -174,6 +176,7 @@ def dump_accounts(): def dump_photos(): from photo_app.models import Photo + print('photo_app.photo') res = [{ 'model': 'photo_app.photo', 'pk': p.pk, @@ -233,6 +236,8 @@ def make_migration(): with open(fname, 'w') as f: dump(func(), f, default=my_date_converter, ensure_ascii=False) + if not os.path.isdir('fixtures'): + os.mkdir('fixtures') django.setup() #appdump(['fixtures', 'group_app', 'groups_fixture.json'], dump_groups) #appdump(['fixtures', 'tariff_app', 'tariffs_fixture.json'], dump_tariffs) From 1915c806d13356c4b90c82a44db8e262f82d1c25 Mon Sep 17 00:00:00 2001 From: bashmak Date: Thu, 8 Mar 2018 17:23:26 +0300 Subject: [PATCH 10/15] it remains to finish the guardians --- migrate_to_0.2.py | 97 ++++++++++++++++++++++++++++++----------------- 1 file changed, 62 insertions(+), 35 deletions(-) diff --git a/migrate_to_0.2.py b/migrate_to_0.2.py index 2d8a7f3..594d434 100755 --- a/migrate_to_0.2.py +++ b/migrate_to_0.2.py @@ -22,15 +22,14 @@ def get_fixture_from_unchanget_model(model_name: str, model_class): def get_fields(obj): fields = dict() for field in obj._meta.get_fields(): - if isinstance(field, django_fields.reverse_related.ManyToOneRel) or \ - isinstance(field, django_fields.reverse_related.ManyToManyRel): + if isinstance(field, (django_fields.reverse_related.ManyToOneRel, django_fields.reverse_related.ManyToManyRel)): continue field_val = getattr(obj, field.name) if field_val is None: continue if isinstance(field, django_fields.related.ManyToManyField): fields[field.name] = [f.pk for f in field_val.all()] - elif isinstance(field, django_fields.related.ForeignKey): + elif isinstance(field, (django_fields.related.ForeignKey, django.contrib.contenttypes.fields.GenericForeignKey)): fields[field.name] = field_val.pk elif isinstance(field, django_fields.FloatField): fields[field.name] = float(field_val) @@ -156,9 +155,11 @@ def dump_accounts(): 'birth_day': up.birth_day, 'is_active': up.is_active, 'is_admin': up.is_admin, - 'telephone': up.telephone + 'telephone': up.telephone, + 'password': up.password, + 'last_login': up.last_login } - } for up in models.UserProfile.objects.exclude(username='AnonymousUser')] + } for up in models.UserProfile.objects.all()] print('accounts_app.userprofile') res += [{ @@ -203,15 +204,6 @@ def dump_map(): return res -def dump_messages(): - from msg_app import models - res = get_fixture_from_unchanget_model('msg_app.messagestatus', models.MessageStatus) - res += get_fixture_from_unchanget_model('msg_app.message', models.Message) - res += get_fixture_from_unchanget_model('msg_app.conversationmembership', models.ConversationMembership) - res += get_fixture_from_unchanget_model('msg_app.conversation', models.Conversation) - return res - - def dump_task_app(): from taskapp import models res = get_fixture_from_unchanget_model('taskapp.changelog', models.ChangeLog) @@ -220,9 +212,22 @@ def dump_task_app(): return res +def dump_auth(): + from django.contrib.auth import models + res = get_fixture_from_unchanget_model('auth.group', models.Group) + res += get_fixture_from_unchanget_model('auth.permission', models.Permission) + return res + + +def dump_guardian(): + from guardian import models + res = get_fixture_from_unchanget_model('guardian.groupobjectpermission', models.GroupObjectPermission) + res += get_fixture_from_unchanget_model('guardian.userobjectpermission', models.UserObjectPermission) + return res + + def make_migration(): - from datetime import datetime - from datetime import date + from datetime import datetime, date def my_date_converter(o): if isinstance(o, datetime) or isinstance(o, date): @@ -238,32 +243,51 @@ def make_migration(): if not os.path.isdir('fixtures'): os.mkdir('fixtures') - django.setup() - #appdump(['fixtures', 'group_app', 'groups_fixture.json'], dump_groups) - #appdump(['fixtures', 'tariff_app', 'tariffs_fixture.json'], dump_tariffs) - #appdump(['fixtures', 'photo_app', 'photos_fixture.json'], dump_photos) - #appdump(['fixtures', 'devapp', 'devs_fixture.json'], dump_devs) + appdump(['fixtures', 'group_app', 'groups_fixture.json'], dump_groups) + appdump(['fixtures', 'tariff_app', 'tariffs_fixture.json'], dump_tariffs) + appdump(['fixtures', 'photo_app', 'photos_fixture.json'], dump_photos) + appdump(['fixtures', 'devapp', 'devs_fixture.json'], dump_devs) appdump(['fixtures', 'accounts_app', 'accounts_fixture.json'], dump_accounts) appdump(['fixtures', 'abonapp', 'abon_fixture.json'], dump_abonapp) - - #appdump(['fixtures', 'chatbot', 'chatbot_fixture.json'], dump_chatbot) - #appdump(['fixtures', 'mapapp', 'map_fixture.json'], dump_map) - #appdump(['fixtures', 'msg_app', 'mesages_fixture.json'], dump_messages) - #appdump(['fixtures', 'taskapp', 'task_fixture.json'], dump_task_app) + appdump(['fixtures', 'chatbot', 'chatbot_fixture.json'], dump_chatbot) + appdump(['fixtures', 'mapapp', 'map_fixture.json'], dump_map) + appdump(['fixtures', 'taskapp', 'task_fixture.json'], dump_task_app) + appdump(['fixtures', 'accounts_app', 'auth_fixture.json'], dump_auth) + appdump(['fixtures', 'accounts_app', 'guardian_fixture.json'], dump_guardian) def move_to_fixtures_dirs(): fixdir = 'fixtures' for dr in os.listdir(fixdir): fixture_dir = os.path.join(fixdir, dr) - fixture_file = os.listdir( fixture_dir )[0] - from_file = os.path.join(fixture_dir, fixture_file) - dst_dir = os.path.join(dr, fixdir) - to_file = os.path.join(dst_dir, fixture_file) - if not os.path.isdir( dst_dir ): - os.mkdir(dst_dir) - shutil.copyfile( from_file, to_file ) - print('cp %s -> %s' % (from_file, to_file)) + for fixture_file in os.listdir(fixture_dir): + from_file = os.path.join(fixture_dir, fixture_file) + dst_dir = os.path.join(dr, fixdir) + to_file = os.path.join(dst_dir, fixture_file) + if not os.path.isdir(dst_dir): + os.mkdir(dst_dir) + shutil.copyfile(from_file, to_file) + print('cp %s -> %s' % (from_file, to_file)) + + +def apply_fixtures(): + from django.core.management import execute_from_command_line + from accounts_app.models import UserProfile + from django.contrib.auth import models + + UserProfile.objects.filter(username='AnonymousUser').delete() + print('clearing auth.group') + models.Group.objects.all().delete() + print('clearing auth.permission') + models.Permission.objects.all().delete() + + print('./manage.py loaddata -v2 ...') + execute_from_command_line([sys.argv[0], 'loaddata', '-v', '2', + 'groups_fixture.json', 'tariffs_fixture.json', 'photos_fixture.json', + 'devs_fixture.json', 'accounts_fixture.json', 'abon_fixture.json', + 'chatbot_fixture.json', 'map_fixture.json', 'task_fixture.json', + 'auth_fixture.json', 'guardian_fixture.json' + ]) if __name__ == '__main__': @@ -272,9 +296,12 @@ if __name__ == '__main__': exit(1) choice = sys.argv[1] if choice == 'applydump': + django.setup() move_to_fixtures_dirs() - print('And now apply created fixtures by ./manage.py loaddata') + apply_fixtures() + os.remove('fixtures') elif choice == 'makedump': + django.setup() make_migration() else: print('Unexpected choice') From c9ec0214ed095268e44ac4922981cc4a1e3b0d87 Mon Sep 17 00:00:00 2001 From: www-data Date: Sun, 11 Mar 2018 20:42:58 +0300 Subject: [PATCH 11/15] fix bugs --- agent/netflow/netflow_handler.py | 5 ++-- .../templates/clientsideapp/index.html | 24 +++++++++---------- devapp/models.py | 2 +- static/clientside/custom.css | 4 ---- systemd_units/djing_rotate.service | 2 +- taskapp/templates/taskapp/tasklist.html | 14 +++++------ .../templates/taskapp/tasklist_failed.html | 10 ++++++-- taskapp/templates/taskapp/tasklist_own.html | 2 +- 8 files changed, 33 insertions(+), 30 deletions(-) diff --git a/agent/netflow/netflow_handler.py b/agent/netflow/netflow_handler.py index f88a4ea..41c23c1 100755 --- a/agent/netflow/netflow_handler.py +++ b/agent/netflow/netflow_handler.py @@ -4,6 +4,7 @@ import sys import os from importlib import import_module + if __name__ == '__main__': if len(sys.argv) < 2: print("File name of netflow required") @@ -48,8 +49,8 @@ if __name__ == '__main__': db.close() os.system( - '/usr/bin/bash -c ' - '"%(CUR_DIR)s/djing_flow %(TMP_IPUSER_FILE)s < %(TMP_DUMP)s | ' + 'bash -c "export LD_LIBRARY_PATH=. && ' + '%(CUR_DIR)s/djing_flow %(TMP_IPUSER_FILE)s < %(TMP_DUMP)s | ' '/usr/bin/mysql -u%(DB_USER)s -h %(HOST)s -p %(DB_NAME)s --password=%(DB_PASSW)s"' % { 'CUR_DIR': cur_dir, 'TMP_IPUSER_FILE': tmp_ipuser_file, diff --git a/clientsideapp/templates/clientsideapp/index.html b/clientsideapp/templates/clientsideapp/index.html index a0ff0b2..3c9b642 100644 --- a/clientsideapp/templates/clientsideapp/index.html +++ b/clientsideapp/templates/clientsideapp/index.html @@ -1,18 +1,6 @@ {% extends 'clientsideapp/ext.html' %} {% block client_main %} -
-
Наши реквизиты
-
-
    -
  • Режим работы: с 9:00 до 22:00
  • -
  • Телефоны: +79788328885, +79788318999
  • -
  • Адрес: пос. Нижнегорский, ул. Победы 38. 2й этаж старого дома быта, возле рынка
  • -
- -
-
-
{% trans 'Name' %}{% trans 'Name and comment count' %} {% trans 'Address' %} {% trans 'The nature of the damage' %} {% trans 'Description' %} {{ task.abon.group.title }}, {{ task.abon.street|default:_('Not assigned') }} {{ task.abon.house|default:_('Not assigned') }}
+
+
Наши реквизиты
+
+
    +
  • Режим работы: с 9:00 до 22:00
  • +
  • Телефоны: +79788328885, +79788318999
  • +
  • Адрес: пос. Нижнегорский, ул. Победы 38. 2й этаж старого дома быта, возле рынка
  • +
+ +
+
+ {% endblock %} diff --git a/devapp/models.py b/devapp/models.py index 7bb24de..97d1353 100644 --- a/devapp/models.py +++ b/devapp/models.py @@ -75,7 +75,7 @@ class Device(models.Model): ) verbose_name = _('Device') verbose_name_plural = _('Devices') - ordering = ['comment'] + ordering = ['id'] def get_abons(self): pass diff --git a/static/clientside/custom.css b/static/clientside/custom.css index 1da3ca9..461ffb8 100644 --- a/static/clientside/custom.css +++ b/static/clientside/custom.css @@ -5,10 +5,6 @@ img.navbar-brand { margin: 0 15px 0 0; } -body > .container { - padding: 60px 15px 0; -} - .table thead { background-color: #ddd; } diff --git a/systemd_units/djing_rotate.service b/systemd_units/djing_rotate.service index 4e1678b..f783804 100644 --- a/systemd_units/djing_rotate.service +++ b/systemd_units/djing_rotate.service @@ -4,7 +4,7 @@ Description=A job for rotate djing netflow data [Service] Environment="PATH=/usr/local/sbin:/usr/local/bin:/usr/bin" Type=oneshot -ExecStart=/bin/bash -c "kill -HUP `cat /run/flow.pid.6343`" +ExecStart=/bin/bash -c "kill -HUP `/bin/cat /run/flow.pid.6343`" User=root Group=root diff --git a/taskapp/templates/taskapp/tasklist.html b/taskapp/templates/taskapp/tasklist.html index 4171d95..60c0138 100644 --- a/taskapp/templates/taskapp/tasklist.html +++ b/taskapp/templates/taskapp/tasklist.html @@ -7,13 +7,13 @@ - - - - - - - + + + + + + + diff --git a/taskapp/templates/taskapp/tasklist_failed.html b/taskapp/templates/taskapp/tasklist_failed.html index c890117..64ff05b 100644 --- a/taskapp/templates/taskapp/tasklist_failed.html +++ b/taskapp/templates/taskapp/tasklist_failed.html @@ -46,8 +46,14 @@ - + - + From 1471437d8a0359648fbec9dc654417878c3a418e Mon Sep 17 00:00:00 2001 From: bashmak Date: Tue, 13 Mar 2018 12:15:27 +0300 Subject: [PATCH 12/15] Add customisable pages and add somthing translates. --- clientsideapp/locale/ru/LC_MESSAGES/django.po | 212 +++++++++++++----- .../clientsideapp/custom_pages/footer.htm | 1 + .../clientsideapp/custom_pages/main_page.htm | 1 + .../clientsideapp/custom_pages/service.htm | 2 + .../custom_pages/service_bottom.htm | 1 + .../templates/clientsideapp/debt_buy.html | 10 +- .../templates/clientsideapp/debts.html | 25 ++- .../templates/clientsideapp/ext.html | 51 ++--- .../templates/clientsideapp/index.html | 71 +----- .../clientsideapp/modal_service_buy.html | 4 +- .../templates/clientsideapp/services.html | 44 ++-- djing/local_settings.py.template | 2 + djing/settings.py | 6 + djing/templatetags/__init__.py | 0 djing/templatetags/globaltags.py | 9 + static/clientside/bc.png | Bin 8907 -> 4259 bytes static/img/bcgr.png | Bin 6114 -> 2036 bytes templates/all_base.html | 6 +- 18 files changed, 244 insertions(+), 201 deletions(-) create mode 100644 clientsideapp/templates/clientsideapp/custom_pages/footer.htm create mode 100644 clientsideapp/templates/clientsideapp/custom_pages/main_page.htm create mode 100644 clientsideapp/templates/clientsideapp/custom_pages/service.htm create mode 100644 clientsideapp/templates/clientsideapp/custom_pages/service_bottom.htm create mode 100644 djing/templatetags/__init__.py create mode 100644 djing/templatetags/globaltags.py diff --git a/clientsideapp/locale/ru/LC_MESSAGES/django.po b/clientsideapp/locale/ru/LC_MESSAGES/django.po index 9fcf3b2..a5706a5 100644 --- a/clientsideapp/locale/ru/LC_MESSAGES/django.po +++ b/clientsideapp/locale/ru/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-02-28 12:46+0300\n" +"POT-Creation-Date: 2018-03-13 11:49+0300\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: Dmitry Novikov nerosketch@gmail.com\n" "Language: ru\n" @@ -19,121 +19,229 @@ msgstr "" "%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" -#: clientsideapp/templates/clientsideapp/debt_buy.html:6 +#: templates/clientsideapp/debt_buy.html:6 msgid "Debts" msgstr "Задолженность" -#: clientsideapp/templates/clientsideapp/debt_buy.html:11 +#: templates/clientsideapp/debt_buy.html:11 msgid "repayment of debts" msgstr "Оплатить задолженность" -#: clientsideapp/templates/clientsideapp/debt_buy.html:18 +#: templates/clientsideapp/debt_buy.html:18 msgid "Are you sure you want to spend a payment?" msgstr "Вы уверены что хотите провести платёж?" -#: clientsideapp/templates/clientsideapp/debt_buy.html:21 -#, fuzzy, python-format -#| msgid "" -#| "From your account, they withdraw funds in %(amount)s rub.
\n" -#| "As a result, you will remain on your account %(ballance_after)s rubles. " -#| "
\n" -#| "The administrator can immediately see that you shut down the debt." +#: templates/clientsideapp/debt_buy.html:22 msgid "" -"From your account, they withdraw funds in %(amount)s rub.
\n" -"As a result, you will remain on your account %(ballance_after)s rubles.
\n" -"The administrator can immediately see that you shut down the debt." +"From your account, they withdraw funds in %(amount)s rub.
As a " +"result, you will remain on your account %(ballance_after)s rubles.
The " +"administrator can immediately see that you shut down the debt." msgstr "" "С вашего счёта снимутся средства в размере %(amount)s руб.
\n" "В результате у вас на счету останется %(ballance_after)s руб.
\n" "Администратор сразу сможет видеть что у вас закрыта задолженность." -#: clientsideapp/templates/clientsideapp/debt_buy.html:24 +#: templates/clientsideapp/debt_buy.html:28 msgid "Description of payment" msgstr "Описание платежа" -#: clientsideapp/templates/clientsideapp/debt_buy.html:32 +#: templates/clientsideapp/debt_buy.html:36 msgid "Confirm" msgstr "Подтвердить" -#: clientsideapp/templates/clientsideapp/debt_buy.html:35 +#: templates/clientsideapp/debt_buy.html:39 msgid "Cancel" msgstr "Отменить" -#: clientsideapp/templates/clientsideapp/modal_service_buy.html:5 +#: templates/clientsideapp/debts.html:6 +msgid "Your debt" +msgstr "Ваши долги" + +#: templates/clientsideapp/debts.html:11 +msgid "State" +msgstr "Состояние" + +#: templates/clientsideapp/debts.html:12 +msgid "Summ" +msgstr "Сумма" + +#: templates/clientsideapp/debts.html:13 +msgid "Description" +msgstr "Описание" + +#: templates/clientsideapp/debts.html:14 +msgid "Date of create" +msgstr "Дата создания" + +#: templates/clientsideapp/debts.html:15 +msgid "Date of pay" +msgstr "Дата платежа" + +#: templates/clientsideapp/debts.html:16 templates/clientsideapp/debts.html:38 +msgid "Pay" +msgstr "Оплатить" + +#: templates/clientsideapp/debts.html:23 templates/clientsideapp/ext.html:63 +#: templates/clientsideapp/modal_service_buy.html:18 +#: templates/clientsideapp/services.html:26 +#: templates/clientsideapp/services.html:55 +msgid "currency" +msgstr "руб." + +#: templates/clientsideapp/debts.html:31 +msgid "Created paid" +msgstr "Создан оплаченным" + +#: templates/clientsideapp/debts.html:33 +msgid "Not yet paid" +msgstr "Ещё не оплачен" + +#: templates/clientsideapp/debts.html:45 +msgid "You have no debt" +msgstr "У вас нет долгов" + +#: templates/clientsideapp/ext.html:7 templates/clientsideapp/ext.html:41 +msgid "Personal account" +msgstr "Личный кабинет" + +#: templates/clientsideapp/ext.html:46 +msgid "Pays" +msgstr "Платежи" + +#: templates/clientsideapp/ext.html:51 +msgid "Services" +msgstr "Услуги" + +#: templates/clientsideapp/ext.html:55 +msgid "Other" +msgstr "Другое" + +#: templates/clientsideapp/ext.html:57 +msgid "Show debts and pay it" +msgstr "Посмотреть долги и оплатить" + +#: templates/clientsideapp/ext.html:58 +msgid "Quit" +msgstr "Выйти" + +#: templates/clientsideapp/ext.html:63 +#, python-format +msgid "Your balance is %(ballance)s" +msgstr "Ваш балланс %(ballance)s" + +#: templates/clientsideapp/ext.html:74 +msgid "" +"Attantion! You are is admin, and do not be active here, " +"please back to admin side. Client side to you for reference only." +msgstr "" +"Кстати. Вы администратор, и не должны производить действия " +"из кабинета пользователя, производите их из админки. Кабинет клиента для вас " +"только для ознакомления." + +#: templates/clientsideapp/modal_service_buy.html:5 msgid "Pick service" msgstr "Заказать услугу" -#: clientsideapp/templates/clientsideapp/modal_service_buy.html:8 +#: templates/clientsideapp/modal_service_buy.html:8 msgid "Are you sure you want to order the service?" msgstr "Вы уверены что хотите заказать услугу?" +#: templates/clientsideapp/modal_service_buy.html:9 +msgid "" +"Be careful, after purchasing the service you will withdraw money, and " +"you will be able to use the purchased service." +msgstr "" +"Будте внимательны, после заказа услуги с вашего счёта снимутся средства, " +"и вы сможете пользоваться купленной услугой." -#: clientsideapp/templates/clientsideapp/modal_service_buy.html:28 +#: templates/clientsideapp/modal_service_buy.html:15 +#, python-format +msgid "" +"Inbound speed: %(speedIn)s MBit/s
\n" +"Outgoing speed: %(speedOut)s MBit/s
\n" +"Cost: %(amount)s rubles." +msgstr "" + +#: templates/clientsideapp/modal_service_buy.html:18 +#, python-format +msgid "The cost is %(amount)s" +msgstr "Стоимость %(amount)s" + +#: templates/clientsideapp/modal_service_buy.html:22 +#: templates/clientsideapp/services.html:63 msgid "Pick" msgstr "Заказать" -#: clientsideapp/templates/clientsideapp/modal_service_buy.html:30 +#: templates/clientsideapp/modal_service_buy.html:24 msgid "Close" msgstr "Закрыть" -#: clientsideapp/templates/clientsideapp/pays.html:6 +#: templates/clientsideapp/pays.html:6 msgid "conducted payments" msgstr "Проведённые платежи" -#: clientsideapp/templates/clientsideapp/pays.html:11 +#: templates/clientsideapp/pays.html:11 msgid "Transaction Amount (rubles)" msgstr "Сумма транзакции (руб)" -#: clientsideapp/templates/clientsideapp/pays.html:12 +#: templates/clientsideapp/pays.html:12 msgid "Date of transaction" msgstr "Дата транзакции" -#: clientsideapp/templates/clientsideapp/pays.html:13 +#: templates/clientsideapp/pays.html:13 msgid "Comment" msgstr "Комментарий" -#: clientsideapp/templates/clientsideapp/pays.html:25 +#: templates/clientsideapp/pays.html:25 msgid "You have not spent payments" msgstr "У вас нет проведённых платежей" -#: clientsideapp/views.py:53 -#, python-format -msgid "Buy the service via user side, service '%s'" -msgstr "Покупка тарифного плана через личный кабинет, тариф '%s'" +#: templates/clientsideapp/services.html:14 +msgid "Your current service" +msgstr "Ваша текущая услуга" -#: clientsideapp/views.py:82 -#, python-format -msgid "Service '%s' has been finished" -msgstr "Услуга '%s' успешно завершена" +#: templates/clientsideapp/services.html:19 +msgid "The date of connection" +msgstr "Дата подключения" -#: clientsideapp/views.py:87 -#, python-format -msgid "Early terminated service '%s' via client side" -msgstr "Досрочное завершение услуги '%s' из личного кабинета" +#: templates/clientsideapp/services.html:22 +msgid "The date of finish service" +msgstr "Дата завершения услуги" + +#: templates/clientsideapp/services.html:25 +msgid "Cost" +msgstr "Стоимость" + +#: templates/clientsideapp/services.html:35 +msgid "" +"Attantion! You have not yet a service, for use the services " +"please purchase service you want." +msgstr "" +"Внимание! У вас нет услуги, для использования ресурсов приобретите " +"нужную услугу из представленных тут." -#: clientsideapp/views.py:90 clientsideapp/views.py:119 -#: clientsideapp/views.py:147 -msgid "Act is not confirmed" -msgstr "Действие не подтверждено" +#: templates/clientsideapp/services.html:46 +msgid "Services available for ordering" +msgstr "Доступные для заказа услуги" -#: clientsideapp/views.py:103 clientsideapp/views.py:130 -msgid "Temporary network bug" -msgstr "Временные неполадки в сети" +#: templates/clientsideapp/services.html:68 +msgid "No services available for ordering" +msgstr "Нет доступных для заказа услуг" -#: clientsideapp/views.py:126 -msgid "The service was not found" -msgstr "Указанная подписка на услугу не найдена" +#: views.py:51 +#, python-format +msgid "Buy the service via user side, service '%s'" +msgstr "Покупка тарифного плана через личный кабинет, тариф '%s'" -#: clientsideapp/views.py:145 +#: views.py:53 #, python-format msgid "The service '%s' wan successfully activated" msgstr "Услуга '%s' успешно подключена" -#: clientsideapp/views.py:181 +#: views.py:83 msgid "Are you not sure that you want buy the service?" msgstr "Вы не уверены что хотите оплатить долг?" -#: clientsideapp/views.py:183 +#: views.py:85 msgid "Your account have not enough money" msgstr "Недостаточно средств на счету" diff --git a/clientsideapp/templates/clientsideapp/custom_pages/footer.htm b/clientsideapp/templates/clientsideapp/custom_pages/footer.htm new file mode 100644 index 0000000..55ce634 --- /dev/null +++ b/clientsideapp/templates/clientsideapp/custom_pages/footer.htm @@ -0,0 +1 @@ +Your custom content here. \ No newline at end of file diff --git a/clientsideapp/templates/clientsideapp/custom_pages/main_page.htm b/clientsideapp/templates/clientsideapp/custom_pages/main_page.htm new file mode 100644 index 0000000..27e1fca --- /dev/null +++ b/clientsideapp/templates/clientsideapp/custom_pages/main_page.htm @@ -0,0 +1 @@ +

You can change this page

\ No newline at end of file diff --git a/clientsideapp/templates/clientsideapp/custom_pages/service.htm b/clientsideapp/templates/clientsideapp/custom_pages/service.htm new file mode 100644 index 0000000..a987da4 --- /dev/null +++ b/clientsideapp/templates/clientsideapp/custom_pages/service.htm @@ -0,0 +1,2 @@ +

Your custom content

+

You have service variable {{ active_service }}

\ No newline at end of file diff --git a/clientsideapp/templates/clientsideapp/custom_pages/service_bottom.htm b/clientsideapp/templates/clientsideapp/custom_pages/service_bottom.htm new file mode 100644 index 0000000..89cbb76 --- /dev/null +++ b/clientsideapp/templates/clientsideapp/custom_pages/service_bottom.htm @@ -0,0 +1 @@ +

Your custom content on bottom

\ No newline at end of file diff --git a/clientsideapp/templates/clientsideapp/debt_buy.html b/clientsideapp/templates/clientsideapp/debt_buy.html index 510fbe8..a607273 100644 --- a/clientsideapp/templates/clientsideapp/debt_buy.html +++ b/clientsideapp/templates/clientsideapp/debt_buy.html @@ -18,9 +18,13 @@ {% trans 'Are you sure you want to spend a payment?' %} -

{% blocktrans %}From your account, they withdraw funds in {{ amount }} rub.
-As a result, you will remain on your account {{ ballance_after }} rubles.
-The administrator can immediately see that you shut down the debt.{% endblocktrans %}

+

+ {% blocktrans trimmed %} + From your account, they withdraw funds in {{ amount }} rub.
+ As a result, you will remain on your account {{ ballance_after }} rubles.
+ The administrator can immediately see that you shut down the debt. + {% endblocktrans %} +

{% trans 'Description of payment' %}

diff --git a/clientsideapp/templates/clientsideapp/debts.html b/clientsideapp/templates/clientsideapp/debts.html index d7d1038..18563e3 100644 --- a/clientsideapp/templates/clientsideapp/debts.html +++ b/clientsideapp/templates/clientsideapp/debts.html @@ -1,25 +1,26 @@ {% extends 'clientsideapp/ext.html' %} +{% load i18n %} {% block client_main %}
{% trans 'Name and comment count' %}{% trans 'Address' %}{% trans 'The nature of the damage' %}{% trans 'Description' %}{% trans 'Task author' %}{% trans 'Date of create' %}{% trans 'Actions' %}{% trans 'Name and comment count' %}{% trans 'Address' %}{% trans 'The nature of the damage' %}{% trans 'Description' %}{% trans 'Task author' %}{% trans 'Actions' %}
{{ task.get_mode_display }} {{ task.descr }}{{ task.author.username }} + {% if task.author %} + {{ task.author.username }} + {% else %} + {% trans 'Not assigned' %} + {% endif %} + diff --git a/taskapp/templates/taskapp/tasklist_own.html b/taskapp/templates/taskapp/tasklist_own.html index bd12cd2..b4bc996 100644 --- a/taskapp/templates/taskapp/tasklist_own.html +++ b/taskapp/templates/taskapp/tasklist_own.html @@ -52,7 +52,7 @@ {% endif %} {{ task.get_mode_display }}{{ task.descr }}{{ task.descr|default:'' }} {{ task.get_state_display }}
- - - - - - + + + + + + {% for debt in debts %} - + {% empty %} - + {% endfor %} diff --git a/clientsideapp/templates/clientsideapp/ext.html b/clientsideapp/templates/clientsideapp/ext.html index bdd7c4d..2ee4627 100644 --- a/clientsideapp/templates/clientsideapp/ext.html +++ b/clientsideapp/templates/clientsideapp/ext.html @@ -1,10 +1,10 @@ - +{% load globaltags %}{% load i18n %} - Личный кабинет + {% global_var 'COMPANY_NAME' %} - {% trans 'Personal account' %} @@ -19,6 +19,8 @@ + +{% url 'client_side:home' as client_side_home %} @@ -80,11 +70,12 @@
{% if request.user.is_staff %} -
- Кстати. - Вы администратор, и не должны производить действия из кабинета пользователя, производите их из админки. - Кабинет клиента для вас только для ознакомления. -
+
+ {% blocktrans trimmed %} + Attantion! + You are is admin, and do not be active here, please back to admin side. Client side to you for reference only. + {% endblocktrans %} +
{% endif %} {% include 'message_block.html' %} @@ -95,7 +86,7 @@ diff --git a/clientsideapp/templates/clientsideapp/index.html b/clientsideapp/templates/clientsideapp/index.html index a0ff0b2..a881d0e 100644 --- a/clientsideapp/templates/clientsideapp/index.html +++ b/clientsideapp/templates/clientsideapp/index.html @@ -1,73 +1,4 @@ {% extends 'clientsideapp/ext.html' %} {% block client_main %} - -
-
Наши реквизиты
-
-
    -
  • Режим работы: с 9:00 до 22:00
  • -
  • Телефоны: +79788328885, +79788318999
  • -
  • Адрес: пос. Нижнегорский, ул. Победы 38. 2й этаж старого дома быта, возле рынка
  • -
- -
-
- -
СостояниеСуммаОписаниеДата созданияДата платежаОплатить{% trans 'State' %}{% trans 'Summ' %}{% trans 'Description' %}{% trans 'Date of create' %}{% trans 'Date of pay' %}{% trans 'Pay' %}
{{ debt.amount }} руб.{{ debt.amount }} {% trans 'currency' %} {{ debt.comment }} {{ debt.date_create|date:'d b H:i' }} @@ -27,21 +28,21 @@ {{ debt.date_pay|date:'d b H:i' }} {% else %} {% if debt.status %} - Создан оплаченным + {% trans 'Created paid' %} {% else %} - Ещё не оплачен + {% trans 'Not yet paid' %} {% endif %} {% endif %} - +
У вас нет долгов {% trans 'You have no debt' %}
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
УслугаСтоимость (руб)
Выезд мастера. Цена зависит от удалённости от Нижнегорска100-200
Настройка роутера300
Установка и восстановление сетевых настроек100
Замена кабеля (витая пара)30 руб/метр
Замена конектора RJ-4550
Роутер TL-WR840N + настройка1800
Установка драйвера (с настройкой сетевых установок)100
"Что-то не работает" – по договорённости~
Замена блока питания200
Замена оптоволоконных линий связиот 500
- - + {% include 'clientsideapp/custom_pages/main_page.htm' %} {% endblock %} diff --git a/clientsideapp/templates/clientsideapp/modal_service_buy.html b/clientsideapp/templates/clientsideapp/modal_service_buy.html index 93d6d7d..4c46754 100644 --- a/clientsideapp/templates/clientsideapp/modal_service_buy.html +++ b/clientsideapp/templates/clientsideapp/modal_service_buy.html @@ -6,7 +6,7 @@ + {% include 'clientsideapp/custom_pages/service_bottom.htm' %} - - -
-
-
-
- - Как работает личный кабинет, раздел услуг -
-
-

Перед вами находится 2 столбца, в левом ваша текущая подключённая услуга, а в правом доступные для заказа услуги. - Когда у вас уже есть подключённая услуга то заказать новую вы не можете пока не завершится текущая.

-

Когда у вас нет действующей подключённой услуги то кнопка заказа станет активной, и вы сможете заказать для себя услугу. - Обратите внимание что именно в этот момент с вашего счёта снимутся деньги в соответствии со стоимостью услуги. -

-
-
-
-
{% endblock %} diff --git a/djing/local_settings.py.template b/djing/local_settings.py.template index 8f251a4..f3ac692 100644 --- a/djing/local_settings.py.template +++ b/djing/local_settings.py.template @@ -57,3 +57,5 @@ API_AUTH_SECRET = 'your api secret' # Allowed subnet for api API_AUTH_SUBNET = '127.0.0.0/8' +# Company name +COMPANY_NAME = 'Your company name' diff --git a/djing/settings.py b/djing/settings.py index cdeb813..ef5e0bc 100644 --- a/djing/settings.py +++ b/djing/settings.py @@ -86,6 +86,9 @@ TEMPLATES = [ 'global_context_processors.context_processor_additional_profile', 'msg_app.context_processors.get_new_messages_count' ], + 'libraries': { + 'globaltags': 'djing.templatetags.globaltags', + } }, }, ] @@ -187,3 +190,6 @@ API_AUTH_SECRET = local_settings.API_AUTH_SECRET # Allowed subnet for api API_AUTH_SUBNET = local_settings.API_AUTH_SUBNET + +# Company name +COMPANY_NAME = local_settings.COMPANY_NAME diff --git a/djing/templatetags/__init__.py b/djing/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/djing/templatetags/globaltags.py b/djing/templatetags/globaltags.py new file mode 100644 index 0000000..9b36fd9 --- /dev/null +++ b/djing/templatetags/globaltags.py @@ -0,0 +1,9 @@ +from django import template +from django.conf import settings + +register = template.Library() + + +@register.simple_tag +def global_var(var_name): + return getattr(settings, var_name, '') diff --git a/static/clientside/bc.png b/static/clientside/bc.png index d61c54d4f6370e8b97928e2e3e3fda9f5cbfaa87..c0e9ec3b5e1a61563725adb1e2aa07fc707f1007 100644 GIT binary patch literal 4259 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 Z@BeHTSKT?qo3#J{002ovPDHLkV1g5?{gwa# diff --git a/static/img/bcgr.png b/static/img/bcgr.png index ebe8b5e2156c9f2b0b0014597ea3ce7dba2f73ed..b70f55d393b2dab5271e0a507f2ab62a469dd04a 100644 GIT binary patch literal 2036 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 +{% load globaltags %} - InternetService - Админка + {% global_var 'COMPANY_NAME' %} - Админка @@ -37,7 +37,7 @@ - +