Browse Source

Merge branch 'devel' of https://github.com/nerosketch/djing into devel

# Conflicts:
#	abonapp/forms.py
#	abonapp/locale/ru/LC_MESSAGES/django.po
#	abonapp/models.py
devel
bashmak 8 years ago
parent
commit
3a16578491
  1. 45
      abonapp/forms.py
  2. 38
      abonapp/locale/ru/LC_MESSAGES/django.po
  3. 37
      abonapp/models.py
  4. 14
      abonapp/templates/abonapp/modal_export.html
  5. 30
      abonapp/templates/abonapp/peoples.html
  6. 1
      abonapp/urls_abon.py
  7. 53
      abonapp/views.py
  8. 3
      accounts_app/models.py
  9. 22
      agent/netflow/netflow_handler.sh
  10. 6
      devapp/views.py
  11. 5
      static/css/custom.css
  12. 2
      taskapp/templates/taskapp/tasklist.html

45
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'))

38
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 "<a href='%(user_url)s'>%(user_name)s</a> already pinned to this port on this device"
msgstr "<a href='%(user_url)s'>%(user_name)s</a> уже привязан к этому порту на этом устройстве"
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"

37
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.<TariffBase>.manage_access()
# is subscriber have access to service, view in tariff_app.custom_tariffs.<TariffBase>.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

14
abonapp/templates/abonapp/modal_export.html

@ -0,0 +1,14 @@
{% load i18n %}
{% load bootstrap3 %}
<form role="form" action="{% url 'abonapp:abon_export' gid %}?f=csv" method="post"> {% csrf_token %}
<div class="modal-header primary">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title"><span class="glyphicon glyphicon-plus"></span>{% trans 'Select the fields' %}</h4>
</div>
<div class="modal-body">
{% bootstrap_form form %}
<button type="submit" class="btn btn-sm btn-primary">
<span class="glyphicon glyphicon-export"></span> {% trans 'Export' %}
</button>
</div>
</form>

30
abonapp/templates/abonapp/peoples.html

@ -3,14 +3,13 @@
{% load dpagination %}
{% block main %}
<ol class="breadcrumb">
<li><span class="glyphicon glyphicon-home"></span></li>
<li><a href="{% url 'abonapp:group_list' %}">{% trans 'User groups' %}</a></li>
<li class="active">{{ abon_group.title }}</li>
</ol>
<ol class="breadcrumb">
<li><span class="glyphicon glyphicon-home"></span></li>
<li><a href="{% url 'abonapp:group_list' %}">{% trans 'User groups' %}</a></li>
<li class="active">{{ abon_group.title }}</li>
</ol>
{% include 'message_block.html' %}
{% include 'message_block.html' %}
<h3>{% trans 'The people in the selected group' %}</h3>
<div class="row">
@ -21,32 +20,32 @@
<tr>
<th>#</th>
<th class="col-xs-1">
<a href="{% url 'abonapp:people_list' abon_group.pk %}?{% url_replace request order_by=username dir=dir|default:"down" %}">
<a href="{% url 'abonapp:people_list' abon_group.pk %}?{% url_replace request order_by='username' dir=dir|default:'down' %}">
{% trans 'Sub' %}
</a>
{% if order_by == 'username' %}<span class="glyphicon glyphicon-filter"></span>{% endif %}
</th>
<th class="hidden-xs">{% trans 'Last traffic' %}</th>
<th class="col-xs-1 hidden-md">
<a href="{% url 'abonapp:people_list' abon_group.pk %}?{% url_replace request order_by=ip_address dir=dir|default:"down" %}">
<a href="{% url 'abonapp:people_list' abon_group.pk %}?{% url_replace request order_by='ip_address' dir=dir|default:'down' %}">
{% trans 'Ip address' %}
</a>
{% if order_by == 'ip_address' %}<span class="glyphicon glyphicon-filter"></span>{% endif %}
</th>
<th class="col-xs-2">
<a href="{% url 'abonapp:people_list' abon_group.pk %}?{% url_replace request order_by=fio dir=dir|default:"down" %}">
<a href="{% url 'abonapp:people_list' abon_group.pk %}?{% url_replace request order_by='fio' dir=dir|default:'down' %}">
{% trans 'fio' %}
</a>
{% if order_by == 'fio' %}<span class="glyphicon glyphicon-filter"></span>{% endif %}
</th>
<th class="col-xs-2">
<a href="{% url 'abonapp:people_list' abon_group.pk %}?{% url_replace request order_by=street dir=dir|default:"down" %}">
<a href="{% url 'abonapp:people_list' abon_group.pk %}?{% url_replace request order_by='street' dir=dir|default:'down' %}">
{% trans 'Street' %}
</a>
{% if order_by == 'street' %}<span class="glyphicon glyphicon-filter"></span>{% endif %}
</th>
<th class="col-xs-1">
<a href="{% url 'abonapp:people_list' abon_group.pk %}?{% url_replace request order_by=house dir=dir|default:"down" %}">
<a href="{% url 'abonapp:people_list' abon_group.pk %}?{% url_replace request order_by='house' dir=dir|default:'down' %}">
{% trans 'Apartment' %}
</a>
{% if order_by == 'house' %}<span class="glyphicon glyphicon-filter"></span>{% endif %}
@ -54,7 +53,7 @@
<th class="col-xs-2">{% trans 'Telephone' %}</th>
<th class="col-xs-2">{% trans 'Service' %}</th>
<th class="hidden-xs col-sm-1">
<a href="{% url 'abonapp:people_list' abon_group.pk %}?{% url_replace request order_by=ballance dir=dir|default:"down" %}">
<a href="{% url 'abonapp:people_list' abon_group.pk %}?{% url_replace request order_by='ballance' dir=dir|default:'down' %}">
{% trans 'Ballance' %}
</a>
{% if order_by == 'ballance' %}<span class="glyphicon glyphicon-filter"></span>{% endif %}
@ -137,6 +136,9 @@
<a href="{% url 'abonapp:phonebook' abon_group.pk %}" class="btn btn-default btn-modal">
<span class="glyphicon glyphicon-earphone"></span> {% trans 'Phonebook' %}
</a>
<a href="{% url 'abonapp:abon_export' abon_group.pk %}" class="btn btn-default btn-modal">
<span class="glyphicon glyphicon-export"></span> {% trans 'Export users' %}
</a>
</td>
</tr>
</tfoot>
@ -165,6 +167,6 @@
</div>
</div>
{% include 'toolbar_page.html' with pag=peoples %}
{% include 'toolbar_page.html' with pag=peoples %}
{% endblock %}

1
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<sid>\d+)/delete$', views.street_del, name='street_del'),

53
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, _("<a href='%(user_url)s'>%(user_name)s</a> 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, _("<a href='%(user_url)s'>%(user_name)s</a> 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'))

3
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']

22
agent/netflow/netflow_handler.sh

@ -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 <IP Database> -p <DBUSER> --password=<DB_PASSWORD>
rm $TMP_DUMP

6
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', {

5
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;
}

2
taskapp/templates/taskapp/tasklist.html

@ -44,7 +44,7 @@
{% endif %}
<td>{{ task.get_mode_display }}</td>
<td>{{ task.descr }}</td>
<td>{{ task.descr|default:'' }}</td>
<td>
{% if task.author %}
<a href="{% url 'acc_app:other_profile' task.author.pk %}" data-toggle="tooltip" title="{{ task.author.get_full_name }}">{{ task.author.username }}</a>

Loading…
Cancel
Save