diff --git a/abonapp/forms.py b/abonapp/forms.py index 4f7da47..9068eda 100644 --- a/abonapp/forms.py +++ b/abonapp/forms.py @@ -16,7 +16,7 @@ def generate_random_username(length=6, chars=digits, split=2, delimiter=''): username = ''.join([choice(chars) for i in range(length)]) if split: - username = delimiter.join([username[start:start+split] for start in range(0, len(username), split)]) + username = delimiter.join([username[start:start + split] for start in range(0, len(username), split)]) try: models.Abon.objects.get(username=username) @@ -26,7 +26,7 @@ def generate_random_username(length=6, chars=digits, split=2, delimiter=''): def generate_random_password(): - return generate_random_username(length=8, chars=digits+ascii_lowercase) + return generate_random_username(length=8, chars=digits + ascii_lowercase) class AbonForm(forms.ModelForm): @@ -41,14 +41,16 @@ class AbonForm(forms.ModelForm): if abon_group_queryset is not None: self.fields['street'].queryset = abon_group_queryset - username = forms.CharField(max_length=127, required=False, initial=generate_random_username, widget=forms.TextInput(attrs={ - 'placeholder': _('login'), - 'class': "form-control", - 'required': '' - })) + username = forms.CharField(max_length=127, required=False, initial=generate_random_username, + widget=forms.TextInput(attrs={ + 'placeholder': _('login'), + 'class': "form-control", + 'required': '' + })) password = forms.CharField(max_length=64, initial=generate_random_password, - widget=forms.TextInput(attrs={'class': 'form-control', 'type': 'password', 'autocomplete': 'new-password'})) + widget=forms.TextInput( + attrs={'class': 'form-control', 'type': 'password', 'autocomplete': 'new-password'})) class Meta: model = models.Abon @@ -116,7 +118,6 @@ class PassportForm(forms.ModelForm): class ExtraFieldForm(forms.ModelForm): - class Meta: model = models.ExtraFieldsModel fields = '__all__' @@ -132,7 +133,7 @@ class AbonStreetForm(forms.ModelForm): model = models.AbonStreet fields = '__all__' widgets = { - 'name': forms.TextInput(attrs={'class': 'form-control', 'required':'', 'autofocus':''}), + 'name': forms.TextInput(attrs={'class': 'form-control', 'required': '', 'autofocus': ''}), 'group': forms.Select(attrs={'class': 'form-control'}) } @@ -148,7 +149,7 @@ class AdditionalTelephoneForm(forms.ModelForm): 'required': '', 'class': 'form-control' }), - 'owner_name': forms.TextInput(attrs={'class': 'form-control', 'required':''}) + 'owner_name': forms.TextInput(attrs={'class': 'form-control', 'required': ''}) } @@ -156,3 +157,25 @@ class PeriodicPayForIdForm(forms.ModelForm): class Meta: model = models.PeriodicPayForId exclude = ['account'] + + +class ExportUsersForm(forms.Form): + FIELDS_CHOICES = ( + ('username', _('profile username')), + ('fio', _('fio')), + ('ip_address', _('Ip Address')), + ('description', _('Comment')), + ('street__name', _('Street')), + ('house', _('House')), + ('birth_day', _('birth day')), + ('is_active', _('Is active')), + ('telephone', _('Telephone')), + ('current_tariff__tariff__title', _('Service title')), + ('ballance', _('Ballance')), + ('device__comment', _('Device')), + ('dev_port__descr', _('Device port')), + ('is_dynamic_ip', _('Is dynamic ip')) + ) + fields = forms.MultipleChoiceField(choices=FIELDS_CHOICES, + widget=forms.CheckboxSelectMultiple(attrs={"checked": ""}), + label=_('Fields')) diff --git a/abonapp/locale/ru/LC_MESSAGES/django.po b/abonapp/locale/ru/LC_MESSAGES/django.po index 7ed374f..d2fbdc2 100644 --- a/abonapp/locale/ru/LC_MESSAGES/django.po +++ b/abonapp/locale/ru/LC_MESSAGES/django.po @@ -469,7 +469,7 @@ msgstr "Добавить муфту" #: templates/abonapp/editAbon.html:141 msgid "Device port" -msgstr "Порт устройства" +msgstr "Порт устройства" #: templates/abonapp/editAbon.html:158 msgid "Is dynamic network settings" @@ -1076,3 +1076,39 @@ msgstr "Инфо." msgid "Dialing" msgstr "Звонки" + +msgid "No have ip" +msgstr "Нет ip адреса" + +#, python-format +msgid "Graph of use by %(wantdate_d)s" +msgstr "График использования за %(wantdate_d)s" + +msgid "Show graph by date" +msgstr "Показать график по дате" + +#: views.py:757 +#, python-format +msgid "%(user_name)s already pinned to this port on this device" +msgstr "%(user_name)s уже привязан к этому порту на этом устройстве" + +msgid "Export" +msgstr "Экспорт" + +msgid "Export users" +msgstr "Экспорт абонентов" + +msgid "Fields" +msgstr "Поля" + +msgid "Select the fields" +msgstr "Выберите поля" + +msgid "Service title" +msgstr "Название тарифа" + +msgid "Is dynamic ip" +msgstr "Динамический ip" + +msgid "Unexpected format %(export_format)s" +msgstr "Нежиданный формат %(export_format)s" diff --git a/abonapp/models.py b/abonapp/models.py index 20a4509..34e59a1 100644 --- a/abonapp/models.py +++ b/abonapp/models.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from datetime import datetime from django.core.exceptions import ValidationError from django.core.validators import RegexValidator @@ -57,23 +56,21 @@ class AbonLog(models.Model): class AbonTariff(models.Model): tariff = models.ForeignKey(Tariff, models.CASCADE, related_name='linkto_tariff') - # время начала действия услуги time_start = models.DateTimeField(null=True, blank=True, default=None) - # время завершения услуги deadline = models.DateTimeField(null=True, blank=True, default=None) def calc_amount_service(self): amount = self.tariff.amount return round(amount, 2) - # Используется-ли услуга сейчас, если время старта есть то он активирован + # is used service now, if time start is present than it activated def is_started(self): return False if self.time_start is None else True def __str__(self): - return "%d: %s" % ( - self.pk or 0, + return "%s: %s" % ( + self.deadline, self.tariff.title ) @@ -143,7 +140,6 @@ class ExtraFieldsModel(models.Model): db_table = 'abon_extra_fields' - class AbonManager(MyUserManager): def get_queryset(self): @@ -163,7 +159,6 @@ class Abon(UserProfile): dev_port = models.ForeignKey('devapp.Port', null=True, blank=True, on_delete=models.SET_NULL) is_dynamic_ip = models.BooleanField(default=False) - # возвращает связь с текущим тарифом для абонента def active_tariff(self): return self.current_tariff @@ -190,7 +185,6 @@ class Abon(UserProfile): self.save(update_fields=['ballance']) post_save.connect(abon_post_save, sender=Abon) - # Пополняем счёт def add_ballance(self, current_user, amount, comment): AbonLog.objects.create( abon=self, @@ -200,7 +194,6 @@ class Abon(UserProfile): ) self.ballance += amount - # покупаем тариф def pick_tariff(self, tariff, author, comment=None, deadline=None): if not isinstance(tariff, Tariff): raise TypeError @@ -212,13 +205,13 @@ class Abon(UserProfile): if self.current_tariff is not None: if self.current_tariff.tariff == tariff: - # Эта услуга уже подключена + # if service already connected raise LogicError(_('That service already activated')) else: - # Не надо молча заменять услугу если какая-то уже есть + # if service is present then speak about it raise LogicError(_('Service already activated')) - # если не хватает денег + # if not enough money if self.ballance < amount: raise LogicError(_('not enough money')) @@ -226,30 +219,30 @@ class Abon(UserProfile): new_abtar.save() self.current_tariff = new_abtar - # снимаем деньги за услугу + # charge for the service self.ballance -= amount self.save() - # Запись об этом в лог + # make log about it AbonLog.objects.create( abon=self, amount=-tariff.amount, author=author, comment=comment or _('Buy service default log') ) - # Производим расчёт услуги абонента, т.е. завершаем если пришло время + # Destroy the service if the time has come def bill_service(self, author): abon_tariff = self.active_tariff() if abon_tariff is None: return nw = timezone.now() - # если услуга просрочена + # if service is overdue if nw > abon_tariff.deadline: print("Service %s for user %s is overdued, end service" % (abon_tariff.tariff, self)) abon_tariff.delete() - # есть-ли доступ у абонента к услуге, смотрим в tariff_app.custom_tariffs..manage_access() + # is subscriber have access to service, view in tariff_app.custom_tariffs..manage_access() def is_access(self): abon_tariff = self.active_tariff() if abon_tariff is None: @@ -258,7 +251,7 @@ class Abon(UserProfile): ct = trf.get_calc_type()(abon_tariff) return ct.manage_access(self) - # создаём абонента из структуры агента + # make subscriber from agent structure def build_agent_struct(self): if self.ip_address: user_ip = ip2int(self.ip_address) @@ -273,7 +266,7 @@ class Abon(UserProfile): return AbonStruct(self.pk, user_ip, agent_trf, bool(self.is_active)) def save(self, *args, **kwargs): - # проверяем не-ли у кого такого-же ip + # check if ip address already busy if self.ip_address is not None and Abon.objects.filter(ip_address=self.ip_address).exclude(pk=self.pk).count() > 0: self.is_bad_ip = True raise LogicError(_('Ip address already exist')) @@ -455,10 +448,8 @@ def abon_post_save(sender, **kwargs): try: tm = Transmitter() if created: - # создаём абонента tm.add_user(agent_abon, ip_timeout=timeout) else: - # обновляем абонента на NAS tm.update_user(agent_abon, ip_timeout=timeout) except (NasFailedResult, NasNetworkError, ConnectionResetError) as e: @@ -473,9 +464,7 @@ def abon_del_signal(sender, **kwargs): ab = abon.build_agent_struct() if ab is None: return True - # подключаемся к NAS'у tm = Transmitter() - # нашли абонента, и удаляем его на NAS tm.remove_user(ab) except (NasFailedResult, NasNetworkError): return True diff --git a/abonapp/templates/abonapp/modal_export.html b/abonapp/templates/abonapp/modal_export.html new file mode 100644 index 0000000..1444209 --- /dev/null +++ b/abonapp/templates/abonapp/modal_export.html @@ -0,0 +1,14 @@ +{% load i18n %} +{% load bootstrap3 %} +
{% csrf_token %} + + +
diff --git a/abonapp/templates/abonapp/peoples.html b/abonapp/templates/abonapp/peoples.html index 7ccc764..7b68bf5 100644 --- a/abonapp/templates/abonapp/peoples.html +++ b/abonapp/templates/abonapp/peoples.html @@ -3,14 +3,13 @@ {% load dpagination %} {% block main %} + - - - {% include 'message_block.html' %} +{% include 'message_block.html' %}

{% trans 'The people in the selected group' %}

@@ -21,32 +20,32 @@ # - + {% trans 'Sub' %} {% if order_by == 'username' %}{% endif %} {% trans 'Last traffic' %} - + {% trans 'Ip address' %} {% if order_by == 'ip_address' %}{% endif %} - + {% trans 'fio' %} {% if order_by == 'fio' %}{% endif %} - + {% trans 'Street' %} {% if order_by == 'street' %}{% endif %} - + {% trans 'Apartment' %} {% if order_by == 'house' %}{% endif %} @@ -54,7 +53,7 @@ {% trans 'Telephone' %} {% trans 'Service' %} - + {% trans 'Ballance' %} {% if order_by == 'ballance' %}{% endif %} @@ -137,6 +136,9 @@ {% trans 'Phonebook' %} + + {% trans 'Export users' %} + @@ -165,6 +167,6 @@
- {% include 'toolbar_page.html' with pag=peoples %} +{% include 'toolbar_page.html' with pag=peoples %} {% endblock %} diff --git a/abonapp/urls_abon.py b/abonapp/urls_abon.py index d8efa20..13cf863 100644 --- a/abonapp/urls_abon.py +++ b/abonapp/urls_abon.py @@ -7,6 +7,7 @@ urlpatterns = [ url(r'^addabon$', views.addabon, name='add_abon'), url(r'^services$', views.chgroup_tariff, name='ch_group_tariff'), url(r'^phonebook$', views.phonebook, name='phonebook'), + url(r'^export$', views.abon_export, name='abon_export'), url(r'^street/add$', views.street_add, name='street_add'), url(r'^street/edit', views.street_edit, name='street_edit'), url(r'^street/(?P\d+)/delete$', views.street_del, name='street_del'), diff --git a/abonapp/views.py b/abonapp/views.py index ad7f498..9ab948d 100644 --- a/abonapp/views.py +++ b/abonapp/views.py @@ -763,18 +763,22 @@ def save_user_dev_port(request, gid, uid): if abon.device is not None: try: other_abon = models.Abon.objects.get(device=abon.device, dev_port=port) - user_url = resolve_url('abonapp:abon_home', other_abon.group.id, other_abon.id) - messages.error(request, _("%(user_name)s already pinned to this port on this device") % { - 'user_url': user_url, - 'user_name': other_abon.get_full_name() - }) - return redirect('abonapp:abon_home', gid, uid) + if other_abon != abon: + user_url = resolve_url('abonapp:abon_home', other_abon.group.id, other_abon.id) + messages.error(request, _("%(user_name)s already pinned to this port on this device") % { + 'user_url': user_url, + 'user_name': other_abon.get_full_name() + }) + return redirect('abonapp:abon_home', gid, uid) except models.Abon.DoesNotExist: pass abon.dev_port = port if abon.is_dynamic_ip != is_dynamic_ip: - abon.is_dynamic_ip = is_dynamic_ip + if is_dynamic_ip == 'on': + abon.is_dynamic_ip = True + else: + abon.is_dynamic_ip = False abon.save(update_fields=['dev_port', 'is_dynamic_ip']) else: abon.save(update_fields=['dev_port']) @@ -913,6 +917,41 @@ def phonebook(request, gid): }, request=request) +@login_required +@permission_required('abonapp.can_view_abongroup', (models.AbonGroup, 'pk', 'gid')) +def abon_export(request, gid): + res_format = request.GET.get('f') + + if request.method == 'POST': + frm = forms.ExportUsersForm(request.POST) + if frm.is_valid(): + cleaned_data = frm.clean() + fields = cleaned_data.get('fields') + subscribers = models.Abon.objects.filter(group__id=gid).only(*fields).values_list(*fields) + if res_format == 'csv': + import csv + response = HttpResponse(content_type='text/csv') + response['Content-Disposition'] = 'attachment; filename="users.csv"' + writer = csv.writer(response, quoting=csv.QUOTE_NONNUMERIC) + display_values = [f[1] for f in frm.fields['fields'].choices if f[0] in fields] + writer.writerow(display_values) + for row in subscribers: + writer.writerow(row) + return response + else: + messages.info(request, _('Unexpected format %(export_format)s') % {'export_format': res_format}) + return redirect('abonapp:group_list') + else: + messages.error(request, _('fix form errors')) + return redirect('abonapp:group_list') + else: + frm = forms.ExportUsersForm() + return render_to_text('abonapp/modal_export.html', { + 'gid': gid, + 'form': frm + }, request=request) + + @login_required @permission_required('abonapp.change_abon') @permission_required('abonapp.can_view_abongroup', (models.AbonGroup, 'pk', 'gid')) diff --git a/accounts_app/models.py b/accounts_app/models.py index ca53866..360a914 100644 --- a/accounts_app/models.py +++ b/accounts_app/models.py @@ -3,7 +3,7 @@ import os from django.db import models from django.contrib.auth.models import BaseUserManager, AbstractBaseUser, PermissionsMixin from django.core.validators import RegexValidator -from django.utils.translation import ugettext as _ +from django.utils.translation import gettext_lazy as _ from django.conf import settings from photo_app.models import Photo @@ -108,3 +108,4 @@ class UserProfile(AbstractBaseUser, PermissionsMixin): ) verbose_name = _('Staff account profile') verbose_name_plural = _('Staff account profiles') + ordering = ['fio'] diff --git a/agent/netflow/netflow_handler.sh b/agent/netflow/netflow_handler.sh deleted file mode 100755 index c5c873e..0000000 --- a/agent/netflow/netflow_handler.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env bash - -FNAME="$1" - -if [[ -z "$FNAME" ]]; then - echo "Нужно имя файла дампа netflow" - exit 1 -fi - -CUR_DIR=`dirname $0` - -DUMP_FILE="/tmp/djing_flow/$FNAME" -PATH=/usr/local/sbin:/usr/local/bin:/usr/bin -TMP_DUMP=/tmp/djing_flow/djing_flow_dump.tmp - -cd $CUR_DIR -mkdir -p /tmp/djing_flow -mv $DUMP_FILE $TMP_DUMP - -./djing_flow < $TMP_DUMP | /usr/bin/mysql -uUSER -h -p --password= - -rm $TMP_DUMP diff --git a/devapp/views.py b/devapp/views.py index 201fbc2..17760c5 100644 --- a/devapp/views.py +++ b/devapp/views.py @@ -71,7 +71,7 @@ def devices_null_group(request): def devdel(request, device_id): try: dev = Device.objects.get(pk=device_id) - back_url = resolve_url('devapp:devs', grp=dev.user_group.pk if dev.user_group else 0) + back_url = resolve_url('devapp:devs', group_id=dev.user_group.pk if dev.user_group else 0) dev.delete() return res_success(request, back_url) except Device.DoesNotExist: @@ -162,7 +162,7 @@ def manage_ports(request, device_id): except Device.DoesNotExist: messages.error(request, _('Device does not exist')) - return redirect('devapp:view', dev.user_group.pk if dev.user_group else 0, did=device_id) + return redirect('devapp:group_list') except DeviceDBException as e: messages.error(request, e) return render(request, 'devapp/manage_ports/list.html', { @@ -273,7 +273,7 @@ def edit_single_port(request, group_id, device_id, port_id): messages.success(request, _('Port successfully saved')) else: messages.error(request, _('Form is invalid, check fields and try again')) - return redirect('devapp:manage_ports', group_id, port_id) + return redirect('devapp:manage_ports', group_id, device_id) frm = PortForm(instance=port) return render_to_text('devapp/manage_ports/modal_add_edit_port.html', { diff --git a/static/css/custom.css b/static/css/custom.css index 0fe073e..ade7ac7 100644 --- a/static/css/custom.css +++ b/static/css/custom.css @@ -242,9 +242,8 @@ button[data-toggle=offcanvas]{ * Цвет заливки графика */ .ct-series-a .ct-area { - fill: black; - opacity: 0.3; - fill-opacity: 0.3; + fill: #367cb8; + fill-opacity: 0.4; } diff --git a/taskapp/templates/taskapp/tasklist.html b/taskapp/templates/taskapp/tasklist.html index c4f5130..9568e15 100644 --- a/taskapp/templates/taskapp/tasklist.html +++ b/taskapp/templates/taskapp/tasklist.html @@ -44,7 +44,7 @@ {% endif %} {{ task.get_mode_display }} - {{ task.descr }} + {{ task.descr|default:'' }} {% if task.author %} {{ task.author.username }}