from datetime import datetime
from typing import Dict, Optional
from kombu.exceptions import OperationalError
from abonapp.tasks import customer_nas_command, customer_nas_remove
from agent.commands.dhcp import dhcp_commit, dhcp_expiry, dhcp_release
from devapp.models import Device, Port as DevPort
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
from django.core.exceptions import PermissionDenied, ValidationError
from django.db import IntegrityError, transaction
from django.db.models import Count
from django.http import (
HttpResponse, HttpResponseBadRequest,
HttpResponseRedirect
)
from django.shortcuts import render, redirect, get_object_or_404, resolve_url
from django.urls import reverse_lazy
from django.utils.decorators import method_decorator
from django.utils.translation import gettext_lazy as _
from django.views.generic import ListView, UpdateView, CreateView, DeleteView, DetailView
from djing import lib
from djing.global_base_views import OrderedFilteredList, SecureApiView
from djing.lib.decorators import json_view, only_admins
from djing.lib.mixins import (
OnlyAdminsMixin,
LoginAdminPermissionMixin,
LoginAdminMixin
)
from group_app.models import Group
from guardian.decorators import \
permission_required_or_403 as permission_required
#from guardian.shortcuts import get_objects_for_user, assign_perm
from gw_app.models import NASModel
from gw_app.nas_managers import NasFailedResult, NasNetworkError
from ip_pool.models import NetworkModel
from tariff_app.models import Tariff
from taskapp.models import Task
from abonapp import forms
from abonapp import models
class PeoplesListView(LoginRequiredMixin, OnlyAdminsMixin,
OrderedFilteredList):
template_name = 'abonapp/peoples.html'
def get_queryset(self):
street_id = lib.safe_int(self.request.GET.get('street'))
gid = lib.safe_int(self.kwargs.get('gid'))
peoples_list = models.Abon.objects.filter(group__pk=gid)
if street_id > 0:
peoples_list = peoples_list.filter(street=street_id)
peoples_list = peoples_list.select_related(
'group', 'street', 'current_tariff__tariff', 'statcache'
).only(
'group', 'street', 'fio', 'birth_day',
'street', 'house', 'telephone', 'ballance', 'markers',
'username', 'is_active', 'current_tariff', 'ip_address'
)
ordering = self.get_ordering()
if ordering and isinstance(ordering, str):
ordering = (ordering,)
peoples_list = peoples_list.order_by(*ordering)
return peoples_list
def get_context_data(self, **kwargs):
gid = lib.safe_int(self.kwargs.get('gid'))
if gid < 1:
return HttpResponseBadRequest('group id is broken')
group = get_object_or_404(Group, pk=gid)
if not self.request.user.has_perm('group_app.view_group', group):
raise PermissionDenied
context = super(PeoplesListView, self).get_context_data(**kwargs)
context['streets'] = models.AbonStreet.objects.filter(
group=gid
).only('name')
context['street_id'] = lib.safe_int(self.request.GET.get('street'))
context['group'] = group
return context
class GroupListView(LoginRequiredMixin, OnlyAdminsMixin, OrderedFilteredList):
context_object_name = 'groups'
template_name = 'abonapp/group_list.html'
def get_queryset(self):
#queryset = get_objects_for_user(
# self.request.user,
# 'group_app.view_group', klass=Group,
# use_groups=False,
# accept_global_perms=False
#)
queryset = Group.objects.all()
return queryset.annotate(usercount=Count('abon'))
class AbonCreateView(LoginRequiredMixin, OnlyAdminsMixin,
PermissionRequiredMixin, CreateView):
permission_required = 'abonapp.add_abon'
group = None
abon = None
form_class = forms.AbonForm
model = models.Abon
template_name = 'abonapp/addAbon.html'
context_object_name = 'group'
def dispatch(self, request, *args, **kwargs):
group = get_object_or_404(Group, pk=self.kwargs.get('gid'))
if not request.user.has_perm('group_app.view_group', group):
raise PermissionDenied
self.group = group
return super(AbonCreateView, self).dispatch(request, *args, **kwargs)
def get_initial(self):
return {
'group': self.group,
'address': _('Address'),
'is_active': False
}
def get_context_data(self, **kwargs):
context = super(AbonCreateView, self).get_context_data(**kwargs)
context['group'] = self.group
return context
def form_valid(self, form):
try:
abon = form.save()
me = self.request.user
#assign_perm("abonapp.change_abon", me, abon)
#assign_perm("abonapp.delete_abon", me, abon)
#assign_perm("abonapp.can_buy_tariff", me, abon)
#assign_perm('abonapp.can_add_ballance', me, abon)
me.log(self.request.META, 'cusr', '%s, "%s", %s' % (
abon.username, abon.fio,
abon.group.title if abon.group else ''
))
messages.success(self.request, _('create abon success msg'))
self.abon = abon
return super(AbonCreateView, self).form_valid(form)
except (
IntegrityError,
NasFailedResult,
NasNetworkError,
lib.LogicError
) as e:
messages.error(self.request, e)
except lib.MultipleException as errs:
for err in errs.err_list:
messages.error(self.request, err)
return self.render_to_response(self.get_context_data(form=form))
def form_invalid(self, form):
messages.error(self.request, _('fix form errors'))
return super(AbonCreateView, self).form_invalid(form)
class DelAbonDeleteView(LoginAdminMixin, PermissionRequiredMixin, DeleteView):
permission_required = 'abonapp.delete_abon'
model = models.Abon
slug_url_kwarg = 'uname'
slug_field = 'username'
success_url = reverse_lazy('abonapp:group_list')
context_object_name = 'abon'
def get_object(self, queryset=None):
abon = super(DelAbonDeleteView, self).get_object(queryset)
if not self.request.user.has_perm('group_app.view_group', abon.group):
raise PermissionDenied
return abon
def delete(self, request, *args, **kwargs):
try:
abon = self.get_object()
gid = abon.group.id
if abon.current_tariff:
abon_tariff = abon.current_tariff.tariff
customer_nas_remove.delay(
customer_uid=abon.pk, ip_addr=abon.ip_address,
speed=(abon_tariff.speedIn, abon_tariff.speedOut),
is_access=abon.is_access(), nas_pk=abon.nas_id
)
abon.delete()
request.user.log(request.META, 'dusr', (
'%(uname)s, "%(fio)s", %(group)s %(street)s %(house)s' % {
'uname': abon.username,
'fio': abon.fio or '-',
'group': abon.group.title if abon.group else '',
'street': abon.street.name if abon.street else '',
'house': abon.house or ''
}).strip())
messages.success(request, _('delete abon success msg'))
return redirect('abonapp:people_list', gid=gid)
except (NasNetworkError, OperationalError) as e:
messages.error(self.request, e)
except NasFailedResult as e:
messages.error(self.request, _("NAS says: '%s'") % e)
except lib.MultipleException as errs:
for err in errs.err_list:
messages.error(self.request, err)
return HttpResponseRedirect(self.success_url)
@login_required
@only_admins
@permission_required('abonapp.can_add_ballance')
@transaction.atomic
def abonamount(request, gid: int, uname):
abon = get_object_or_404(models.Abon, username=uname)
frm = None
try:
if request.method == 'POST':
frm = forms.AmountMoneyForm(request.POST)
if frm.is_valid():
amnt = frm.cleaned_data.get('amount')
comment = frm.cleaned_data.get('comment')
if not comment:
comment = _('fill account through admin side')
abon.add_ballance(request.user, amnt, comment=comment)
abon.save(update_fields=('ballance',))
messages.success(
request, _('Account filled successfully on %.2f') % amnt
)
return redirect('abonapp:abon_home', gid=gid, uname=uname)
else:
messages.error(request, _('I not know the account id'))
else:
frm = forms.AmountMoneyForm()
except (NasNetworkError, NasFailedResult) as e:
messages.error(request, e)
except lib.MultipleException as errs:
for err in errs.err_list:
messages.error(request, err)
return render(request, 'abonapp/modal_abonamount.html', {
'abon': abon,
'group_id': gid,
'form': frm
})
class DebtsListView(LoginAdminPermissionMixin, OrderedFilteredList):
permission_required = 'group_app.view_group'
context_object_name = 'invoices'
template_name = 'abonapp/invoiceForPayment.html'
def get_permission_object(self):
if not hasattr(self, 'abon'):
abon = get_object_or_404(models.Abon, username=self.kwargs.get('uname'))
self.abon = abon
return self.abon.group
def get_queryset(self):
if not hasattr(self, 'abon'):
abon = get_object_or_404(models.Abon, username=self.kwargs.get('uname'))
if not hasattr(self, 'abon'):
self.abon = abon
return models.InvoiceForPayment.objects.filter(abon=self.abon)
def get_context_data(self, **kwargs):
context = super(DebtsListView, self).get_context_data(**kwargs)
context['group'] = self.abon.group
context['abon'] = self.abon
return context
class AbonServices(LoginAdminMixin, DetailView):
model = models.Abon
slug_url_kwarg = 'uname'
slug_field = 'username'
template_name = 'abonapp/service.html'
context_object_name = 'abon'
def get_object(self, queryset=None):
gid = self.kwargs.get('gid')
abon = super().get_object(queryset)
if abon.group.pk != gid:
messages.warning(
self.request,
_("User group id is not matches with group in url")
)
return redirect('abonapp:abon_services', abon.group.pk, abon.username)
if not self.request.user.has_perm('group_app.view_group', abon.group):
raise PermissionDenied
return abon
def get_context_data(self, **kwargs):
abon = self.object
periodic_pay = models.PeriodicPayForId.objects.filter(
account=abon
).first()
context = {
'abon_tariff': abon.current_tariff,
'group': abon.group,
'services': Tariff.objects.get_tariffs_by_group(abon.group.pk),
'periodic_pay': periodic_pay
}
context.update(kwargs)
return super().get_context_data(**context)
class AbonHomeUpdateView(LoginAdminMixin, PermissionRequiredMixin, UpdateView):
permission_required = 'abonapp.view_abon'
model = models.Abon
form_class = forms.AbonForm
slug_field = 'username'
slug_url_kwarg = 'uname'
template_name = 'abonapp/editAbon.html'
context_object_name = 'abon'
group = None
def dispatch(self, request, *args, **kwargs):
try:
return super(AbonHomeUpdateView, self).dispatch(
request, *args,
**kwargs
)
except lib.LogicError as e:
messages.error(request, e)
except (NasFailedResult, NasNetworkError) as e:
messages.error(request, e)
except lib.MultipleException as errs:
for err in errs.err_list:
messages.error(request, err)
return self.render_to_response(self.get_context_data())
def get_object(self, queryset=None):
gid = self.kwargs.get('gid')
self.group = get_object_or_404(Group, pk=gid)
if not self.request.user.has_perm('group_app.view_group', self.group):
raise PermissionDenied
return super(AbonHomeUpdateView, self).get_object(queryset)
def form_valid(self, form):
r = super(AbonHomeUpdateView, self).form_valid(form)
abon = self.object
try:
customer_nas_command.delay(abon.pk, 'sync')
except OperationalError as e:
messages.error(self.request, str(e))
else:
messages.success(self.request, _('edit abon success msg'))
return r
def form_invalid(self, form):
messages.warning(self.request, _('fix form errors'))
return super(AbonHomeUpdateView, self).form_invalid(form)
def get(self, request, *args, **kwargs):
r = super(AbonHomeUpdateView, self).get(request, *args, **kwargs)
abon = self.object
if abon.device is None:
messages.warning(request, _('User device was not found'))
return r
def get_initial(self):
abon = self.object
if self.initial:
return self.initial
try:
passw = models.AbonRawPassword.objects.get(
account=abon
).passw_text
return {
'password': passw
}
except models.AbonRawPassword.DoesNotExist:
messages.warning(
self.request,
_('User has not have password, and cannot login')
)
return {'password': ''}
def get_context_data(self, **kwargs):
abon = self.object
device = getattr(abon, 'device')
context = {
'group': self.group,
'device': device,
'dev_ports': DevPort.objects.filter(
device=device) if device else None
}
context.update(kwargs)
return super(AbonHomeUpdateView, self).get_context_data(**context)
@login_required
@only_admins
@permission_required('abonapp.add_invoiceforpayment')
def add_invoice(request, gid: int, uname: str):
abon = get_object_or_404(models.Abon, username=uname)
grp = get_object_or_404(Group, pk=gid)
try:
if request.method == 'POST':
curr_amount = lib.safe_int(request.POST.get('curr_amount'))
comment = request.POST.get('comment')
newinv = models.InvoiceForPayment()
newinv.abon = abon
newinv.amount = curr_amount
newinv.comment = comment
if request.POST.get('status') == 'on':
newinv.status = True
newinv.author = request.user
newinv.save()
messages.success(request, _('Receipt has been created'))
return redirect('abonapp:abon_debts', gid=gid, uname=uname)
except (NasNetworkError, NasFailedResult) as e:
messages.error(request, e)
except lib.MultipleException as errs:
for err in errs.err_list:
messages.error(request, err)
return render(request, 'abonapp/addInvoice.html', {
'abon': abon,
'invcount': models.InvoiceForPayment.objects.filter(abon=abon).count(),
'group': grp
})
@login_required
@only_admins
@permission_required('abonapp.can_buy_tariff')
def pick_tariff(request, gid: int, uname):
grp = get_object_or_404(Group, pk=gid)
abon = get_object_or_404(models.Abon, username=uname)
tariffs = Tariff.objects.get_tariffs_by_group(grp.pk)
try:
if request.method == 'POST':
trf = Tariff.objects.get(pk=request.POST.get('tariff'))
deadline = request.POST.get('deadline')
log_comment = _(
"Service '%(service_name)s' "
"has connected via admin until %(deadline)s") % {
'service_name': trf.title,
'deadline': deadline
}
if deadline:
deadline = datetime.strptime(deadline, '%Y-%m-%dT%H:%M')
abon.pick_tariff(trf, request.user, deadline=deadline, comment=log_comment)
customer_nas_command.delay(abon.pk, 'sync')
messages.success(request, _('Tariff has been picked'))
return redirect('abonapp:abon_services', gid=gid,
uname=abon.username)
except (lib.LogicError, NasFailedResult, NasNetworkError, OperationalError) as e:
messages.error(request, e)
return redirect('abonapp:abon_services', gid=gid, uname=abon.username)
except Tariff.DoesNotExist:
messages.error(request, _('Tariff your picked does not exist'))
except lib.MultipleException as errs:
for err in errs.err_list:
messages.error(request, err)
except ValueError as e:
messages.error(request, "%s: %s" % (_('fix form errors'), e))
selected_tariff = request.GET.get('selected_tariff')
if selected_tariff:
selected_tariff = get_object_or_404(Tariff, pk=selected_tariff)
return render(request, 'abonapp/buy_tariff.html', {
'tariffs': tariffs,
'abon': abon,
'group': grp,
'selected_tariff': selected_tariff
})
@login_required
@only_admins
@permission_required('abonapp.can_complete_service')
def unsubscribe_service(request, gid: int, uname, abon_tariff_id: int):
try:
abon_tariff = get_object_or_404(models.AbonTariff,
pk=int(abon_tariff_id))
abon = abon_tariff.abon
trf = abon_tariff.tariff
customer_nas_remove.delay(
customer_uid=abon.pk, ip_addr=abon.ip_address,
speed=(trf.speedIn, trf.speedOut),
is_access=abon.is_access(), nas_pk=abon.nas_id
)
abon_tariff.delete()
messages.success(request, _('User has been detached from service'))
except NasFailedResult as e:
messages.error(request, e)
except (NasNetworkError, OperationalError) as e:
messages.warning(request, e)
except lib.MultipleException as errs:
for err in errs.err_list:
messages.error(request, err)
return redirect('abonapp:abon_services', gid=gid, uname=uname)
class LogListView(LoginAdminPermissionMixin, ListView):
permission_required = 'abonapp.view_abonlog'
paginate_by = getattr(settings, 'PAGINATION_ITEMS_PER_PAGE', 10)
http_method_names = ('get',)
context_object_name = 'logs'
template_name = 'abonapp/log.html'
model = models.AbonLog
class DebtorsListView(LoginAdminPermissionMixin, ListView):
permission_required = 'abonapp.view_invoiceforpayment'
paginate_by = getattr(settings, 'PAGINATION_ITEMS_PER_PAGE', 10)
http_method_names = ('get',)
context_object_name = 'invoices'
template_name = 'abonapp/debtors.html'
queryset = models.InvoiceForPayment.objects.filter(status=True)
class TaskLogListView(LoginAdminPermissionMixin, ListView):
permission_required = 'group_app.view_group'
paginate_by = getattr(settings, 'PAGINATION_ITEMS_PER_PAGE', 10)
http_method_names = ('get',)
context_object_name = 'tasks'
template_name = 'abonapp/task_log.html'
def get_permission_object(self):
if hasattr(self, 'abon'):
return self.abon.group
else:
return get_object_or_404(models.Group, pk=self.kwargs.get('gid'))
def get_queryset(self):
abon = get_object_or_404(models.Abon,
username=self.kwargs.get('uname'))
self.abon = abon
return Task.objects.filter(abon=abon)
def get_context_data(self, **kwargs):
context = super(TaskLogListView, self).get_context_data(**kwargs)
context['group'] = self.abon.group
context['abon'] = self.abon
return context
class PassportUpdateView(LoginAdminPermissionMixin, UpdateView):
permission_required = 'abonapp.view_passportinfo'
form_class = forms.PassportForm
model = models.PassportInfo
template_name = 'abonapp/modal_passport_view.html'
def get_object(self, queryset=None):
self.abon = get_object_or_404(models.Abon,
username=self.kwargs.get('uname'))
try:
passport_instance = models.PassportInfo.objects.get(
abon=self.abon
)
except models.PassportInfo.DoesNotExist:
passport_instance = None
return passport_instance
def form_valid(self, form):
pi = form.save(commit=False)
pi.abon = self.abon
pi.save()
messages.success(
self.request,
_('Passport information has been saved')
)
return super(PassportUpdateView, self).form_valid(form)
def get_success_url(self):
return resolve_url(
'abonapp:abon_home',
gid=self.kwargs.get('gid'),
uname=self.kwargs.get('uname')
)
def form_invalid(self, form):
messages.error(self.request, _('fix form errors'))
return super(PassportUpdateView, self).form_invalid(form)
def get_context_data(self, **kwargs):
context = {
'group': get_object_or_404(Group, pk=self.kwargs.get('gid')),
'abon': self.abon
}
context.update(kwargs)
return super(PassportUpdateView, self).get_context_data(**context)
class IpUpdateView(LoginAdminPermissionMixin, UpdateView):
permission_required = 'abonapp.change_abon'
form_class = forms.AddIpForm
model = models.Abon
slug_url_kwarg = 'uname'
slug_field = 'username'
template_name = 'abonapp/modal_ip_form.html'
def dispatch(self, request, *args, **kwargs):
try:
return super(IpUpdateView, self).dispatch(request, *args, **kwargs)
except lib.LogicError as e:
messages.error(request, e)
except IntegrityError as e:
str_text = str(e)
if 'abonent_ip_address_nas_id' in str_text and 'duplicate key value' in str_text:
messages.error(request, _('IP address conflict'))
else:
messages.error(request, e)
return self.render_to_response(self.get_context_data(**kwargs))
def form_valid(self, form):
r = super(IpUpdateView, self).form_valid(form)
abon = self.object
try:
customer_nas_command.delay(abon.pk, 'sync')
except OperationalError as e:
messages.error(self.request, str(e))
else:
messages.success(self.request, _('Ip successfully updated'))
return r
def get_context_data(self, **kwargs):
context = super(IpUpdateView, self).get_context_data(**kwargs)
context['group'] = self.object.group
context['abon'] = self.object
return context
@login_required
@only_admins
def chgroup_tariff(request, gid):
grp = get_object_or_404(Group, pk=gid)
if not request.user.has_perm('group_app.change_group', grp):
raise PermissionDenied
if request.method == 'POST':
ma = frozenset(t.id for t in grp.tariff_set.all())
mb = frozenset(map(int, request.POST.getlist('tr')))
sub = ma - mb
add = mb - ma
grp.tariff_set.remove(*sub)
grp.tariff_set.add(*add)
models.Abon.objects.filter(
group=grp,
last_connected_tariff__in=sub
).update(last_connected_tariff=None)
messages.success(request, _('Successfully saved'))
return redirect('abonapp:ch_group_tariff', gid)
tariffs = Tariff.objects.all()
seleted_tariffs_id = tuple(
pk[0] for pk in grp.tariff_set.only('pk').values_list('pk')
)
return render(request, 'abonapp/group_tariffs.html', {
'group': grp,
'seleted_tariffs': seleted_tariffs_id,
'tariffs': tariffs
})
@login_required
@only_admins
@permission_required('abonapp.change_abon')
def dev(request, gid: int, uname):
abon_dev = None
try:
abon = models.Abon.objects.get(username=uname)
if request.method == 'POST':
abon.device = Device.objects.get(pk=request.POST.get('dev'))
abon.save(update_fields=('device',))
messages.success(request, _('Device has successfully attached'))
return redirect('abonapp:abon_home', gid=gid, uname=uname)
else:
abon_dev = abon.device
except Device.DoesNotExist:
messages.warning(
request,
_('Device your selected already does not exist')
)
except models.Abon.DoesNotExist:
messages.error(request, _('Abon does not exist'))
return redirect('abonapp:people_list', gid=gid)
return render(request, 'abonapp/modal_dev.html', {
'devices': Device.objects.filter(group=gid),
'dev': abon_dev,
'gid': gid, 'uname': uname
})
@login_required
@only_admins
@permission_required('abonapp.change_abon')
@permission_required('group_app.view_group', (Group, 'pk', 'gid'))
def clear_dev(request, gid: int, uname):
try:
abon = models.Abon.objects.get(username=uname)
abon.device = None
abon.dev_port = None
abon.is_dynamic_ip = False
abon.save(update_fields=('device', 'dev_port', 'is_dynamic_ip'))
messages.success(request, _('Device has successfully unattached'))
except models.Abon.DoesNotExist:
messages.error(request, _('Abon does not exist'))
return redirect('abonapp:people_list', gid=gid)
return redirect('abonapp:abon_home', gid=gid, uname=uname)
@login_required
@only_admins
@permission_required('abonapp.can_ping')
@json_view
def abon_ping(request, gid: int, uname):
ip = request.GET.get('cmd_param')
status = 1
text = ' %s' % _('no ping')
abon = get_object_or_404(models.Abon, username=uname)
try:
if ip is None:
raise lib.LogicError(_('Ip not passed'))
if abon.nas is None:
return {
'status': 1,
'dat': ''
' %s' % _('gateway required')
}
mngr = abon.nas.get_nas_manager()
ping_result = mngr.ping(ip)
if ping_result is None:
return {
'status': 1,
'dat': text
}
if isinstance(ping_result, tuple):
received, sent = ping_result
if received == 0:
ping_result = mngr.ping(ip, arp=True)
if ping_result is not None and isinstance(ping_result, tuple):
received, sent = ping_result
else:
return {
'status': 1,
'dat': text
}
loses_percent = (
received / sent if sent != 0 else 1
)
ping_result = {'return': received, 'all': sent}
if loses_percent > 1.0:
text = ' %s' % _(
'IP Conflict! %(return)d/%(all)d results'
) % ping_result
elif loses_percent > 0.5:
text = ' %s' % _(
'ok ping, %(return)d/%(all)d loses'
) % ping_result
status = 0
else:
text = ' %s' % _(
'no ping, %(return)d/%(all)d loses'
) % ping_result
except (NasFailedResult, lib.LogicError) as e:
text = str(e)
except NasNetworkError as e:
text = str(e)
return {
'status': status,
'dat': text
}
@login_required
@only_admins
@json_view
def set_auto_continue_service(request, gid: int, uname):
checked = request.GET.get('checked')
checked = True if checked == 'true' else False
abon = get_object_or_404(models.Abon, username=uname)
abon.autoconnect_service = checked
abon.save(update_fields=('autoconnect_service',))
return {
'status': 0
}
@login_required
@only_admins
def vcards(r):
users = models.Abon.objects.exclude(group=None).select_related(
'group',
'street'
).only(
'username', 'fio', 'group__title', 'telephone',
'street__name', 'house'
)
additional_tels = models.AdditionalTelephone.objects.select_related(
'abon',
'abon__group',
'abon__street'
)
response = HttpResponse(content_type='text/x-vcard')
response['Content-Disposition'] = 'attachment; filename="contacts.vcard"'
tmpl = ("BEGIN:VCARD\r\n"
"VERSION:4.0\r\n"
"FN:%(uname)s. %(group_name)s, %(street)s %(house)s\r\n"
"IMPP:sip:%(abon_telephone)s@dial.lo\r\n"
"END:VCARD\r\n")
def _make_vcard():
for ab in users.iterator():
tel = ab.telephone
if tel:
yield tmpl % {
'uname': ab.get_full_name(),
'group_name': ab.group.title,
'street': ab.street.name if ab.street else '',
'house': ab.house,
'abon_telephone': tel
}
if not additional_tels.exists():
return
for add_tel in additional_tels.iterator():
abon = add_tel.abon
yield tmpl % {
'uname': "%s (%s)" % (
add_tel.owner_name, abon.get_full_name()),
'group_name': abon.group.title,
'abon_telephone': add_tel.telephone,
'street': abon.street.name if abon.street else '',
'house': abon.house
}
response.content = _make_vcard()
return response
@login_required
@only_admins
@permission_required('abonapp.change_abon')
def save_user_dev_port(request, gid: int, uname):
if request.method != 'POST':
messages.error(request, _('Method is not POST'))
return redirect('abonapp:abon_home', gid, uname)
user_port = lib.safe_int(request.POST.get('user_port'))
is_dynamic_ip = request.POST.get('is_dynamic_ip')
is_dynamic_ip = True if is_dynamic_ip == 'on' else False
try:
abon = models.Abon.objects.get(username=uname)
if user_port == 0:
port = None
else:
port = DevPort.objects.get(pk=user_port)
if abon.device is not None:
try:
other_abon = models.Abon.objects.get(
device=abon.device,
dev_port=port
)
if other_abon != abon:
user_url = resolve_url(
'abonapp:abon_home',
other_abon.group.id,
other_abon.username
)
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, uname)
except models.Abon.DoesNotExist:
pass
except models.Abon.MultipleObjectsReturned:
messages.error(request,
_('Multiple users on the same device port'))
return redirect('devapp:view', abon.device.group.pk,
abon.device.pk)
abon.dev_port = port
if abon.is_dynamic_ip != is_dynamic_ip:
abon.is_dynamic_ip = is_dynamic_ip
abon.save(update_fields=('dev_port', 'is_dynamic_ip'))
else:
abon.save(update_fields=('dev_port',))
messages.success(request, _('User port has been saved'))
except DevPort.DoesNotExist:
messages.error(request, _('Selected port does not exist'))
except models.Abon.DoesNotExist:
messages.error(request, _('User does not exist'))
return redirect('abonapp:abon_home', gid, uname)
@login_required
@only_admins
@permission_required('abonapp.add_abonstreet')
@permission_required('group_app.view_group', (Group, 'pk', 'gid'))
def street_add(request, gid):
if request.method == 'POST':
frm = forms.AbonStreetForm(request.POST)
if frm.is_valid():
frm.save()
messages.success(request, _('Street successfully saved'))
return redirect('abonapp:people_list', gid)
else:
messages.error(request, _('fix form errors'))
else:
frm = forms.AbonStreetForm(initial={'group': gid})
return render(request, 'abonapp/modal_addstreet.html', {
'form': frm,
'gid': gid
})
@login_required
@only_admins
@permission_required('abonapp.change_abonstreet')
@permission_required('group_app.view_group', (Group, 'pk', 'gid'))
def street_edit(request, gid):
try:
if request.method == 'POST':
for sid, sname in zip(request.POST.getlist('sid'),
request.POST.getlist('sname')):
street = models.AbonStreet.objects.get(pk=sid)
street.name = sname
street.save()
messages.success(request, _('Streets has been saved'))
else:
return render(request, 'abonapp/modal_editstreet.html', {
'gid': gid,
'streets': models.AbonStreet.objects.filter(group=gid)
})
except models.AbonStreet.DoesNotExist:
messages.error(request, _('One of these streets has not been found'))
return redirect('abonapp:people_list', gid)
@login_required
@only_admins
@permission_required('abonapp.delete_abonstreet')
@permission_required('group_app.view_group', (Group, 'pk', 'gid'))
def street_del(request, gid: int, sid: int):
try:
models.AbonStreet.objects.get(pk=sid, group=gid).delete()
messages.success(request, _('The street successfully deleted'))
except models.AbonStreet.DoesNotExist:
messages.error(request, _('The street has not been found'))
return redirect('abonapp:people_list', gid)
@login_required
@only_admins
@permission_required('group_app.view_group', (Group, 'pk', 'gid'))
def active_nets(request, gid):
nets = NetworkModel.objects.filter(groups__id=gid)
return render(request, 'abonapp/modal_current_networks.html', {
'networks': nets
})
@login_required
@only_admins
@permission_required('abonapp.view_additionaltelephones')
@permission_required('group_app.view_group', (Group, 'pk', 'gid'))
def tels(request, gid: int, uname):
abon = get_object_or_404(models.Abon, username=uname)
telephones = abon.additional_telephones.all()
return render(request, 'abonapp/modal_additional_telephones.html', {
'telephones': telephones,
'gid': gid,
'uname': uname
})
@login_required
@only_admins
@permission_required('abonapp.add_additionaltelephone')
def tel_add(request, gid: int, uname):
if request.method == 'POST':
frm = forms.AdditionalTelephoneForm(request.POST)
if frm.is_valid():
new_tel = frm.save(commit=False)
abon = get_object_or_404(models.Abon, username=uname)
new_tel.abon = abon
new_tel.save()
messages.success(request, _('New telephone has been saved'))
return redirect('abonapp:abon_home', gid, uname)
else:
messages.error(request, _('fix form errors'))
else:
frm = forms.AdditionalTelephoneForm()
return render(request, 'abonapp/modal_add_phone.html', {
'form': frm,
'gid': gid,
'uname': uname
})
@login_required
@only_admins
@permission_required('abonapp.delete_additionaltelephone')
def tel_del(request, gid: int, uname):
try:
tid = lib.safe_int(request.GET.get('tid'))
tel = models.AdditionalTelephone.objects.get(pk=tid)
tel.delete()
messages.success(request,
_('Additional telephone successfully deleted'))
except models.AdditionalTelephone.DoesNotExist:
messages.error(request, _('Telephone not found'))
return redirect('abonapp:abon_home', gid, uname)
@login_required
@only_admins
@permission_required('group_app.view_group', (Group, 'pk', 'gid'))
def phonebook(request, gid):
res_format = request.GET.get('f')
t1 = models.Abon.objects.filter(
group__id=int(gid)
).only('telephone', 'fio').values_list(
'telephone', 'fio'
)
t2 = models.AdditionalTelephone.objects.filter(
abon__group__id=gid
).only(
'telephone', 'owner_name'
).values_list(
'telephone', 'owner_name'
)
telephones = tuple(t1) + tuple(t2)
if res_format == 'csv':
import csv
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="phones.csv"'
writer = csv.writer(response, quoting=csv.QUOTE_NONNUMERIC)
for row in telephones:
writer.writerow(row)
return response
return render(request, 'abonapp/modal_phonebook.html', {
'tels': telephones,
'gid': gid
})
@login_required
@only_admins
@permission_required('group_app.view_group', (Group, '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(request, 'abonapp/modal_export.html', {
'gid': gid,
'form': frm
})
@login_required
@only_admins
@permission_required('group_app.view_group', (Group, 'pk', 'gid'))
def add_edit_periodic_pay(request, gid: int, uname, periodic_pay_id=0):
if periodic_pay_id == 0:
if not request.user.has_perm('abonapp.add_periodicpayforid'):
raise PermissionDenied
periodic_pay_instance = models.PeriodicPayForId()
else:
if not request.user.has_perm('abonapp.change_periodicpayforid'):
raise PermissionDenied
periodic_pay_instance = get_object_or_404(
models.PeriodicPayForId,
pk=periodic_pay_id
)
if request.method == 'POST':
frm = forms.PeriodicPayForIdForm(
request.POST,
instance=periodic_pay_instance
)
if frm.is_valid():
abon = get_object_or_404(models.Abon, username=uname)
inst = frm.save(commit=False)
inst.account = abon
inst.save()
messages.success(request, _('Periodic pays has been designated'))
else:
messages.error(request, _('Something wrong in form'))
return redirect('abonapp:abon_services', gid, uname)
else:
frm = forms.PeriodicPayForIdForm(instance=periodic_pay_instance)
return render(request, 'abonapp/modal_periodic_pay.html', {
'form': frm,
'gid': gid,
'uname': uname
})
@login_required
@only_admins
@permission_required('group_app.view_group', (Group, 'pk', 'gid'))
@permission_required('abonapp.delete_periodicpayforid')
def del_periodic_pay(request, gid: int, uname, periodic_pay_id):
periodic_pay_instance = get_object_or_404(
models.PeriodicPayForId,
pk=periodic_pay_id
)
if periodic_pay_instance.account.username != uname:
uname = periodic_pay_instance.account.username
periodic_pay_instance.delete()
messages.success(request, _('Periodic pay successfully deleted'))
return redirect('abonapp:abon_services', gid, uname)
class EditSibscriberMarkers(LoginAdminPermissionMixin, UpdateView):
permission_required = 'abonapp.change_abon'
http_method_names = ('get', 'post')
template_name = 'abonapp/modal_user_markers.html'
form_class = forms.MarkersForm
model = models.Abon
slug_url_kwarg = 'uname'
slug_field = 'username'
def dispatch(self, request, *args, **kwargs):
try:
return super(EditSibscriberMarkers, self).dispatch(
request, *args, **kwargs
)
except ValidationError as e:
messages.error(request, e)
return self.render_to_response(self.get_context_data())
def get_context_data(self, **kwargs):
context = super(EditSibscriberMarkers, self).get_context_data(**kwargs)
context['gid'] = self.kwargs.get('gid')
context['uname'] = self.kwargs.get('uname')
return context
def form_invalid(self, form):
messages.error(self.request, _('fix form errors'))
return super(EditSibscriberMarkers, self).form_invalid(form)
def form_valid(self, form):
v = super(EditSibscriberMarkers, self).form_valid(form)
messages.success(
self.request,
_('User flags has changed successfully')
)
return v
class UserSessionFree(LoginRequiredMixin, OnlyAdminsMixin, PermissionRequiredMixin, UpdateView):
permission_required = 'abonapp.change_abon'
model = models.Abon
slug_url_kwarg = 'uname'
slug_field = 'username'
fields = 'ip_address',
template_name = 'abonapp/modal_confirm_ip_free.html'
def get(self, request, *args, **kwargs):
r = super().get(request, *args, **kwargs)
abon = self.object
if abon.nas is None:
messages.error(self.request, _('gateway required'))
return redirect(
'abonapp:abon_home',
gid=self.kwargs.get('gid'),
uname=self.kwargs.get('uname')
)
return r
def post(self, request, *args, **kwargs):
self.object = self.get_object()
abon = self.object
try:
if abon.ip_address:
abon.free_ip_addr()
customer_nas_command.delay(abon.pk, 'remove')
messages.success(request, _('Ip lease has been freed'))
else:
messages.error(request, _('User not have ip'))
except OperationalError as e:
messages.error(request, e)
return redirect(
'abonapp:abon_home',
gid=self.kwargs.get('gid'),
uname=self.kwargs.get('uname')
)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'gid': self.kwargs.get('gid'),
})
return context
@login_required
@only_admins
@permission_required('abonapp.change_abon')
def attach_nas(request, gid):
if request.method == 'POST':
gateway_id = lib.safe_int(request.POST.get('gateway'))
if gateway_id:
nas = get_object_or_404(NASModel, pk=gateway_id)
customers = models.Abon.objects.filter(group__id=gid)
if customers.exists():
customers.update(nas=nas)
messages.success(
request,
_('Network access server for users in this '
'group, has been updated')
)
return redirect('abonapp:group_list')
else:
messages.warning(request, _('Users not found'))
else:
messages.error(request, _('You must select gateway'))
return render(request, 'abonapp/modal_attach_nas.html', {
'gid': gid,
'nas_list': NASModel.objects.all().iterator()
})
# API's
@login_required
@only_admins
@json_view
def abons(request):
ablist = ({
'id': abn.pk,
'tarif_id': abn.active_tariff().tariff.pk
if abn.active_tariff() is not None else 0,
'ip': abn.ip_address
} for abn in models.Abon.objects.iterator())
tarlist = ({
'id': trf.pk,
'speedIn': trf.speedIn,
'speedOut': trf.speedOut
} for trf in Tariff.objects.all())
data = {
'subscribers': ablist,
'tariffs': tarlist
}
del ablist, tarlist
return data
@login_required
@only_admins
@json_view
def search_abon(request):
word = request.GET.get('s')
if not word:
return None
results = models.Abon.objects.filter(fio__icontains=word)[:8]
return list(
{'id': usr.pk, 'text': "%s: %s" % (usr.username, usr.fio)}
for usr in results
)
class DhcpLever(SecureApiView):
#
# Api view for dhcp event
#
http_method_names = ('get',)
@method_decorator(json_view)
def get(self, request, *args, **kwargs):
data = request.GET.copy()
try:
r = self.on_dhcp_event(data)
if r is not None:
return {'text': r}
return {'status': 'ok'}
except IntegrityError as e:
return {'status': str(e).replace('\n', ' ')}
@staticmethod
def on_dhcp_event(data: Dict) -> Optional[str]:
"""
:param data = {
'client_ip': ip_address('127.0.0.1'),
'client_mac': 'aa:bb:cc:dd:ee:ff',
'switch_mac': 'aa:bb:cc:dd:ee:ff',
'switch_port': 3,
'cmd': 'commit'
}"""
try:
action = data.get('cmd')
if action is None:
return '"cmd" parameter is missing'
client_ip = data.get('client_ip')
if client_ip is None:
return '"client_ip" parameter is missing'
if action == 'commit':
return dhcp_commit(
client_ip, data.get('client_mac'),
data.get('switch_mac'), data.get('switch_port')
)
elif action == 'expiry':
return dhcp_expiry(client_ip)
elif action == 'release':
return dhcp_release(client_ip)
else:
return '"cmd" parameter is invalid: %s' % action
except lib.LogicError as e:
print('LogicError', e)
return str(e)
except lib.DuplicateEntry as e:
print('Duplicate:', e)
return str(e)
class PayHistoryListView(LoginAdminPermissionMixin, OrderedFilteredList):
permission_required = 'group_app.view_group'
context_object_name = 'pay_history'
template_name = 'abonapp/payHistory.html'
def get_permission_object(self):
if hasattr(self, 'abon'):
return self.abon.group
return Group.objects.filter(pk=self.kwargs.get('gid')).first()
def get_queryset(self):
abon = get_object_or_404(models.Abon,
username=self.kwargs.get('uname'))
self.abon = abon
pay_history = models.AbonLog.objects.filter(abon=abon).order_by('-date')
return pay_history
def get_context_data(self, **kwargs):
context = {
'group': self.abon.group,
'abon': self.abon
}
context.update(kwargs)
return super(PayHistoryListView, self).get_context_data(**context)