Browse Source

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

devel
Dmitry Novikov 8 years ago
parent
commit
bf7c175c95
  1. 46
      abonapp/forms.py
  2. 23
      abonapp/locale/ru/LC_MESSAGES/django.po
  3. 41
      abonapp/models.py
  4. 14
      abonapp/templates/abonapp/modal_export.html
  5. 27
      abonapp/templates/abonapp/peoples.html
  6. 1
      abonapp/urls_abon.py
  7. 33
      abonapp/views.py
  8. 1
      accounts_app/models.py

46
abonapp/forms.py

@ -8,7 +8,6 @@ from string import digits, ascii_lowercase
from . import models from . import models
from django.conf import settings from django.conf import settings
TELEPHONE_REGEXP = getattr(settings, 'TELEPHONE_REGEXP', r'^\+[7,8,9,3]\d{10,11}$') TELEPHONE_REGEXP = getattr(settings, 'TELEPHONE_REGEXP', r'^\+[7,8,9,3]\d{10,11}$')
@ -16,7 +15,7 @@ def generate_random_username(length=6, chars=digits, split=2, delimiter=''):
username = ''.join([choice(chars) for i in range(length)]) username = ''.join([choice(chars) for i in range(length)])
if split: 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: try:
models.Abon.objects.get(username=username) models.Abon.objects.get(username=username)
@ -26,7 +25,7 @@ def generate_random_username(length=6, chars=digits, split=2, delimiter=''):
def generate_random_password(): 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): class AbonForm(forms.ModelForm):
@ -41,14 +40,16 @@ class AbonForm(forms.ModelForm):
if abon_group_queryset is not None: if abon_group_queryset is not None:
self.fields['street'].queryset = abon_group_queryset 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, 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: class Meta:
model = models.Abon model = models.Abon
@ -116,7 +117,6 @@ class PassportForm(forms.ModelForm):
class ExtraFieldForm(forms.ModelForm): class ExtraFieldForm(forms.ModelForm):
class Meta: class Meta:
model = models.ExtraFieldsModel model = models.ExtraFieldsModel
fields = '__all__' fields = '__all__'
@ -132,7 +132,7 @@ class AbonStreetForm(forms.ModelForm):
model = models.AbonStreet model = models.AbonStreet
fields = '__all__' fields = '__all__'
widgets = { 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'}) 'group': forms.Select(attrs={'class': 'form-control'})
} }
@ -148,5 +148,27 @@ class AdditionalTelephoneForm(forms.ModelForm):
'required': '', 'required': '',
'class': 'form-control' 'class': 'form-control'
}), }),
'owner_name': forms.TextInput(attrs={'class': 'form-control', 'required':''})
'owner_name': forms.TextInput(attrs={'class': 'form-control', 'required': ''})
} }
class ExportUsersForm(forms.Form):
FIELDS_CHOICES = (
('username', _('profile username')),
('fio', _('fio')),
('ip_address', _('Ip Address')),
('description', _('Comment')),
('street', _('Street')),
('house', _('House')),
('birth_day', _('birth day')),
('is_active', _('Is active')),
('telephone', _('Telephone')),
('current_tariff', _('Service title')),
('ballance', _('Ballance')),
('device', _('Device')),
('dev_port', _('Device port')),
('is_dynamic_ip', _('Is dynamic ip'))
)
fields = forms.MultipleChoiceField(choices=FIELDS_CHOICES,
widget=forms.CheckboxSelectMultiple(attrs={"checked": ""}),
label=_('Fields'))

23
abonapp/locale/ru/LC_MESSAGES/django.po

@ -421,7 +421,7 @@ msgstr "Добавить муфту"
#: templates/abonapp/editAbon.html:171 #: templates/abonapp/editAbon.html:171
msgid "Device port" msgid "Device port"
msgstr "Порт устройства"
msgstr "Порт устройства"
#: templates/abonapp/editAbon.html:190 #: templates/abonapp/editAbon.html:190
msgid "Is dynamic network settings" msgid "Is dynamic network settings"
@ -995,3 +995,24 @@ msgstr "Показать график по дате"
#, python-format #, python-format
msgid "<a href='%(user_url)s'>%(user_name)s</a> already pinned to this port on this device" 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> уже привязан к этому порту на этом устройстве" 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"

41
abonapp/models.py

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
from datetime import datetime from datetime import datetime
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.validators import RegexValidator from django.core.validators import RegexValidator
@ -55,23 +54,21 @@ class AbonLog(models.Model):
class AbonTariff(models.Model): class AbonTariff(models.Model):
tariff = models.ForeignKey(Tariff, models.CASCADE, related_name='linkto_tariff') tariff = models.ForeignKey(Tariff, models.CASCADE, related_name='linkto_tariff')
# время начала действия услуги
time_start = models.DateTimeField(null=True, blank=True, default=None) time_start = models.DateTimeField(null=True, blank=True, default=None)
# время завершения услуги
deadline = models.DateTimeField(null=True, blank=True, default=None) deadline = models.DateTimeField(null=True, blank=True, default=None)
def calc_amount_service(self): def calc_amount_service(self):
amount = self.tariff.amount amount = self.tariff.amount
return round(amount, 2) return round(amount, 2)
# Используется-ли услуга сейчас, если время старта есть то он активирован
# is used service now, if time start is present than it activated
def is_started(self): def is_started(self):
return False if self.time_start is None else True return False if self.time_start is None else True
def __str__(self): def __str__(self):
return "%d: %s" % (
self.pk or 0,
return "%s: %s" % (
self.deadline,
self.tariff.title self.tariff.title
) )
@ -139,7 +136,6 @@ class ExtraFieldsModel(models.Model):
db_table = 'abon_extra_fields' db_table = 'abon_extra_fields'
class AbonManager(MyUserManager): class AbonManager(MyUserManager):
def get_queryset(self): def get_queryset(self):
@ -159,7 +155,6 @@ class Abon(UserProfile):
dev_port = models.ForeignKey('devapp.Port', null=True, blank=True, on_delete=models.SET_NULL) dev_port = models.ForeignKey('devapp.Port', null=True, blank=True, on_delete=models.SET_NULL)
is_dynamic_ip = models.BooleanField(default=False) is_dynamic_ip = models.BooleanField(default=False)
# возвращает связь с текущим тарифом для абонента
def active_tariff(self): def active_tariff(self):
return self.current_tariff return self.current_tariff
@ -177,12 +172,10 @@ class Abon(UserProfile):
verbose_name = _('Abon') verbose_name = _('Abon')
verbose_name_plural = _('Abons') verbose_name_plural = _('Abons')
# Платим за что-то
def make_pay(self, curuser, how_match_to_pay=0.0): def make_pay(self, curuser, how_match_to_pay=0.0):
self.ballance -= how_match_to_pay self.ballance -= how_match_to_pay
self.save(update_fields=['ballance']) self.save(update_fields=['ballance'])
# Пополняем счёт
def add_ballance(self, current_user, amount, comment): def add_ballance(self, current_user, amount, comment):
AbonLog.objects.create( AbonLog.objects.create(
abon=self, abon=self,
@ -192,7 +185,6 @@ class Abon(UserProfile):
) )
self.ballance += amount self.ballance += amount
# покупаем тариф
def pick_tariff(self, tariff, author, comment=None, deadline=None): def pick_tariff(self, tariff, author, comment=None, deadline=None):
if not isinstance(tariff, Tariff): if not isinstance(tariff, Tariff):
raise TypeError raise TypeError
@ -204,13 +196,13 @@ class Abon(UserProfile):
if self.current_tariff is not None: if self.current_tariff is not None:
if self.current_tariff.tariff == tariff: if self.current_tariff.tariff == tariff:
# Эта услуга уже подключена
# if service already connected
raise LogicError(_('That service already activated')) raise LogicError(_('That service already activated'))
else: else:
# Не надо молча заменять услугу если какая-то уже есть
# if service is present then speak about it
raise LogicError(_('Service already activated')) raise LogicError(_('Service already activated'))
# если не хватает денег
# if not enough money
if self.ballance < amount: if self.ballance < amount:
raise LogicError(_('not enough money')) raise LogicError(_('not enough money'))
@ -218,30 +210,30 @@ class Abon(UserProfile):
new_abtar.save() new_abtar.save()
self.current_tariff = new_abtar self.current_tariff = new_abtar
# снимаем деньги за услугу
# charge for the service
self.ballance -= amount self.ballance -= amount
self.save() self.save()
# Запись об этом в лог
# make log about it
AbonLog.objects.create( AbonLog.objects.create(
abon=self, amount=-tariff.amount, abon=self, amount=-tariff.amount,
author=author, author=author,
comment=comment or _('Buy service default log') comment=comment or _('Buy service default log')
) )
# Производим расчёт услуги абонента, т.е. завершаем если пришло время
# Destroy the service if the time has come
def bill_service(self, author): def bill_service(self, author):
abon_tariff = self.active_tariff() abon_tariff = self.active_tariff()
if abon_tariff is None: if abon_tariff is None:
return return
nw = timezone.now() nw = timezone.now()
# если услуга просрочена
# if service is overdue
if nw > abon_tariff.deadline: if nw > abon_tariff.deadline:
print("Service %s for user %s is overdued, end service" % (abon_tariff.tariff, self)) print("Service %s for user %s is overdued, end service" % (abon_tariff.tariff, self))
abon_tariff.delete() 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): def is_access(self):
abon_tariff = self.active_tariff() abon_tariff = self.active_tariff()
if abon_tariff is None: if abon_tariff is None:
@ -250,7 +242,7 @@ class Abon(UserProfile):
ct = trf.get_calc_type()(abon_tariff) ct = trf.get_calc_type()(abon_tariff)
return ct.manage_access(self) return ct.manage_access(self)
# создаём абонента из структуры агента
# make subscriber from agent structure
def build_agent_struct(self): def build_agent_struct(self):
if self.ip_address: if self.ip_address:
user_ip = ip2int(self.ip_address) user_ip = ip2int(self.ip_address)
@ -265,7 +257,7 @@ class Abon(UserProfile):
return AbonStruct(self.pk, user_ip, agent_trf, bool(self.is_active)) return AbonStruct(self.pk, user_ip, agent_trf, bool(self.is_active))
def save(self, *args, **kwargs): 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: 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 self.is_bad_ip = True
raise LogicError(_('Ip address already exist')) raise LogicError(_('Ip address already exist'))
@ -319,7 +311,8 @@ class InvoiceForPayment(models.Model):
class AllTimePayLogManager(models.Manager): class AllTimePayLogManager(models.Manager):
def by_days(self):
@staticmethod
def by_days():
cur = connection.cursor() cur = connection.cursor()
cur.execute(r'SELECT SUM(summ) as alsum, DATE_FORMAT(date_add, "%Y-%m-%d") AS pay_date FROM all_time_pay_log ' cur.execute(r'SELECT SUM(summ) as alsum, DATE_FORMAT(date_add, "%Y-%m-%d") AS pay_date FROM all_time_pay_log '
r'GROUP BY DATE_FORMAT(date_add, "%Y-%m-%d")') r'GROUP BY DATE_FORMAT(date_add, "%Y-%m-%d")')
@ -408,10 +401,8 @@ def abon_post_save(sender, **kwargs):
try: try:
tm = Transmitter() tm = Transmitter()
if created: if created:
# создаём абонента
tm.add_user(agent_abon, ip_timeout=timeout) tm.add_user(agent_abon, ip_timeout=timeout)
else: else:
# обновляем абонента на NAS
tm.update_user(agent_abon, ip_timeout=timeout) tm.update_user(agent_abon, ip_timeout=timeout)
except (NasFailedResult, NasNetworkError, ConnectionResetError) as e: except (NasFailedResult, NasNetworkError, ConnectionResetError) as e:
@ -426,9 +417,7 @@ def abon_del_signal(sender, **kwargs):
ab = abon.build_agent_struct() ab = abon.build_agent_struct()
if ab is None: if ab is None:
return True return True
# подключаемся к NAS'у
tm = Transmitter() tm = Transmitter()
# нашли абонента, и удаляем его на NAS
tm.remove_user(ab) tm.remove_user(ab)
except (NasFailedResult, NasNetworkError): except (NasFailedResult, NasNetworkError):
return True 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>

27
abonapp/templates/abonapp/peoples.html

@ -3,14 +3,13 @@
{% load dpagination %} {% load dpagination %}
{% block main %} {% 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> <h3>{% trans 'The people in the selected group' %}</h3>
<div class="row"> <div class="row">
@ -21,32 +20,32 @@
<tr> <tr>
<th>#</th> <th>#</th>
<th class="col-xs-1"> <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' %} {% trans 'Sub' %}
</a> </a>
{% if order_by == 'username' %}<span class="glyphicon glyphicon-filter"></span>{% endif %} {% if order_by == 'username' %}<span class="glyphicon glyphicon-filter"></span>{% endif %}
</th> </th>
<th class="hidden-xs">{% trans 'Last traffic' %}</th> <th class="hidden-xs">{% trans 'Last traffic' %}</th>
<th class="col-xs-1 hidden-md"> <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' %} {% trans 'Ip address' %}
</a> </a>
{% if order_by == 'ip_address' %}<span class="glyphicon glyphicon-filter"></span>{% endif %} {% if order_by == 'ip_address' %}<span class="glyphicon glyphicon-filter"></span>{% endif %}
</th> </th>
<th class="col-xs-2"> <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' %} {% trans 'fio' %}
</a> </a>
{% if order_by == 'fio' %}<span class="glyphicon glyphicon-filter"></span>{% endif %} {% if order_by == 'fio' %}<span class="glyphicon glyphicon-filter"></span>{% endif %}
</th> </th>
<th class="col-xs-2"> <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' %} {% trans 'Street' %}
</a> </a>
{% if order_by == 'street' %}<span class="glyphicon glyphicon-filter"></span>{% endif %} {% if order_by == 'street' %}<span class="glyphicon glyphicon-filter"></span>{% endif %}
</th> </th>
<th class="col-xs-1"> <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' %} {% trans 'Apartment' %}
</a> </a>
{% if order_by == 'house' %}<span class="glyphicon glyphicon-filter"></span>{% endif %} {% 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 'Telephone' %}</th>
<th class="col-xs-2">{% trans 'Service' %}</th> <th class="col-xs-2">{% trans 'Service' %}</th>
<th class="hidden-xs col-sm-1"> <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' %} {% trans 'Ballance' %}
</a> </a>
{% if order_by == 'ballance' %}<span class="glyphicon glyphicon-filter"></span>{% endif %} {% if order_by == 'ballance' %}<span class="glyphicon glyphicon-filter"></span>{% endif %}
@ -165,6 +164,6 @@
</div> </div>
</div> </div>
{% include 'toolbar_page.html' with pag=peoples %}
{% include 'toolbar_page.html' with pag=peoples %}
{% endblock %} {% endblock %}

1
abonapp/urls_abon.py

@ -7,6 +7,7 @@ urlpatterns = [
url(r'^addabon$', views.addabon, name='add_abon'), url(r'^addabon$', views.addabon, name='add_abon'),
url(r'^services$', views.chgroup_tariff, name='ch_group_tariff'), url(r'^services$', views.chgroup_tariff, name='ch_group_tariff'),
url(r'^phonebook$', views.phonebook, name='phonebook'), 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/add$', views.street_add, name='street_add'),
url(r'^street/edit', views.street_edit, name='street_edit'), url(r'^street/edit', views.street_edit, name='street_edit'),
url(r'^street/(?P<sid>\d+)/delete$', views.street_del, name='street_del'), url(r'^street/(?P<sid>\d+)/delete$', views.street_del, name='street_del'),

33
abonapp/views.py

@ -907,6 +907,39 @@ def phonebook(request, gid):
}, request=request) }, 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)
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 @login_required
@permission_required('abonapp.change_abon') @permission_required('abonapp.change_abon')
@permission_required('abonapp.can_view_abongroup', (models.AbonGroup, 'pk', 'gid')) @permission_required('abonapp.can_view_abongroup', (models.AbonGroup, 'pk', 'gid'))

1
accounts_app/models.py

@ -108,3 +108,4 @@ class UserProfile(AbstractBaseUser, PermissionsMixin):
) )
verbose_name = _('Staff account profile') verbose_name = _('Staff account profile')
verbose_name_plural = _('Staff account profiles') verbose_name_plural = _('Staff account profiles')
ordering = ['fio']
Loading…
Cancel
Save