diff --git a/abonapp/forms.py b/abonapp/forms.py index 4c1bdbe..140f310 100644 --- a/abonapp/forms.py +++ b/abonapp/forms.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from datetime import datetime from django.utils.translation import ugettext as _ from django import forms @@ -7,6 +6,7 @@ from random import choice from string import digits, ascii_lowercase from . import models from django.conf import settings +from djing import IP_ADDR_REGEX TELEPHONE_REGEXP = getattr(settings, 'TELEPHONE_REGEXP', r'^\+[7,8,9,3]\d{10,11}$') @@ -36,29 +36,36 @@ def generate_random_password(): class AbonForm(forms.ModelForm): def __init__(self, *args, **kwargs): super(AbonForm, self).__init__(*args, **kwargs) - if self.instance is not None and self.instance.group is not None: - abon_group_queryset = models.AbonStreet.objects.filter(group=self.instance.group) + instance = getattr(self, 'instance') + if instance is not None and instance.group is not None: + abon_group_queryset = models.AbonStreet.objects.filter(group=instance.group) elif 'group' in self.initial.keys() and self.initial['group'] is not None: abon_group_queryset = models.AbonStreet.objects.filter(group=self.initial['group']) else: abon_group_queryset = None if abon_group_queryset is not None: self.fields['street'].queryset = abon_group_queryset + if instance is not None and instance.is_dynamic_ip: + self.fields['ip_address'].widget.attrs['readonly'] = True username = forms.CharField(max_length=127, required=False, initial=generate_random_username, widget=forms.TextInput(attrs={ 'placeholder': _('login'), 'required': '', 'pattern': r'^\w{1,127}$' - })) + }), label=_('login')) password = forms.CharField(max_length=64, initial=generate_random_password, widget=forms.TextInput(attrs={ - 'class': 'form-control', 'type': 'password', 'autocomplete': 'new-password' - })) + 'type': 'password', 'autocomplete': 'new-password' + }), label=_('Password')) + + ip_address = forms.CharField(widget=forms.TextInput(attrs={ + 'pattern': IP_ADDR_REGEX + }), label=_('Ip Address'), required=False) class Meta: model = models.Abon - fields = ['username', 'telephone', 'fio', 'group', 'description', 'street', 'house', 'is_active'] + fields = ['username', 'telephone', 'fio', 'group', 'description', 'street', 'house', 'is_active', 'ip_address'] widgets = { 'fio': forms.TextInput(attrs={ 'placeholder': _('fio'), diff --git a/abonapp/locale/ru/LC_MESSAGES/django.po b/abonapp/locale/ru/LC_MESSAGES/django.po index 36f757f..99bb948 100644 --- a/abonapp/locale/ru/LC_MESSAGES/django.po +++ b/abonapp/locale/ru/LC_MESSAGES/django.po @@ -876,10 +876,6 @@ msgstr "Нет исполнителей" msgid "Tasks not found" msgstr "Нет задач" -#: templates/abonapp/viewAbon.html:10 -msgid "View the subscriber" -msgstr "Просмотр абонента" - #: templates/abonapp/viewAbon.html:24 msgid "yes,no" msgstr "Да,Нет" diff --git a/abonapp/models.py b/abonapp/models.py index f2840b1..dc036aa 100644 --- a/abonapp/models.py +++ b/abonapp/models.py @@ -136,7 +136,7 @@ class Abon(BaseAccount): current_tariff = models.ForeignKey(AbonTariff, null=True, blank=True, on_delete=models.SET_NULL) group = models.ForeignKey(Group, models.SET_NULL, blank=True, null=True, verbose_name=_('User group')) ballance = models.FloatField(default=0.0) - ip_address = MyGenericIPAddressField(blank=True, null=True) + ip_address = MyGenericIPAddressField(blank=True, null=True, verbose_name=_('Ip Address')) description = models.TextField(_('Comment'), null=True, blank=True) street = models.ForeignKey(AbonStreet, on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_('Street')) house = models.CharField(_('House'), max_length=12, null=True, blank=True) @@ -263,17 +263,18 @@ class Abon(BaseAccount): else: return abon_tariff = self.active_tariff() + if abon_tariff is None: + return trf = abon_tariff.tariff agent_trf = TariffStruct(trf.id, trf.speedIn, trf.speedOut) return AbonStruct(self.pk, user_ip, agent_trf, bool(self.is_active)) - def save(self, *args, **kwargs): + def clean(self): # 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')) - super(Abon, self).save(*args, **kwargs) + raise ValidationError({'ip_address': [gettext('Ip address already exist')]}) + return super(Abon, self).clean() def sync_with_nas(self, created: bool) -> Optional[Exception]: agent_abon = self.build_agent_struct() diff --git a/abonapp/templates/abonapp/editAbon.html b/abonapp/templates/abonapp/editAbon.html index 1524d4c..23f789f 100644 --- a/abonapp/templates/abonapp/editAbon.html +++ b/abonapp/templates/abonapp/editAbon.html @@ -10,86 +10,99 @@

{% trans 'Change subscriber' %}

-
{% csrf_token %} - {% bootstrap_field form.username label_class='col-sm-4' field_class='col-sm-8' form_group_class='form-group-sm' %} - {% bootstrap_field form.fio label_class='col-sm-4' field_class='col-sm-8' form_group_class='form-group-sm' %} + {% if perms.abonapp.change_abon %} + {% url 'abonapp:abon_home' group.pk abon.username as ahlink %} + {% endif %} -
- -
-
- {{ form.telephone }}{{ form.telephone.errors }} - - - - - - - - - - - -
-
-
+ {% csrf_token %} -
- -
-
- - - - - - -
-
-
+ {% bootstrap_field form.username form_group_class='form-group-sm' %} + {% bootstrap_field form.fio form_group_class='form-group-sm' %} - {% bootstrap_field form.street label_class='col-sm-4' field_class='col-sm-8' form_group_class='form-group-sm' %} - {% bootstrap_field form.house label_class='col-sm-4' field_class='col-sm-8' form_group_class='form-group-sm' %} - {% bootstrap_field form.is_active label_class='col-sm-4' field_class='col-sm-8' form_group_class='form-group-sm' %} - {% bootstrap_field form.group label_class='col-sm-4' field_class='col-sm-8' form_group_class='form-group-sm' %} -
- -
-
- {{ form.password }}{{ form.password.errors }} - - - -
-
-
+ {# telephone field #} + {% trans 'Call to' as tx %} + {% bootstrap_button '' button_type='link' icon='earphone' button_class='btn-default' title=tx href='sip:'|add:form.telephone.value size='sm' as btn_call %} - {% bootstrap_field form.description label_class='col-sm-4' field_class='col-sm-8' form_group_class='form-group-sm' %} + {% trans 'Additional telephones' as tx %} + {% url 'abonapp:telephones' group.pk abon.username as url %} + {% bootstrap_button '' button_type='link' icon='list' button_class='btn-default btn-modal' title=tx href=url size='sm' as btn_teleph_list %} + + {% trans 'Add telephone' as tx %} + {% url 'abonapp:telephone_new' group.pk abon.username as url %} + {% bootstrap_button '' button_type='link' icon='plus' button_class='btn-default btn-modal' title=tx href=url size='sm' as btn_teleph_add %} + + {% with ''|add:btn_call|add:btn_teleph_list|add:btn_teleph_add as bt %} + {% bootstrap_field form.telephone form_group_class='form-group-sm' addon_after_class='input-group-btn' addon_after=bt %} + {% endwith %} + + + {# Ip address field #} + {% trans 'Reset ip' as tx %} + {% url 'abonapp:reset_ip' group.pk abon.username as url %} + {% bootstrap_button '' button_type='link' icon='refresh' button_class='btn-default btn-cmd' id='iprefreshbtn' href=url size='sm' title=tx as bt %} + {% bootstrap_field form.ip_address form_group_class='form-group-sm' addon_after_class='input-group-btn' addon_after=bt %} + + + {% bootstrap_field form.street form_group_class='form-group-sm' %} + {% bootstrap_field form.house form_group_class='form-group-sm' %} + {% bootstrap_field form.is_active form_group_class='form-group-sm' %} + {% bootstrap_field form.group form_group_class='form-group-sm' %} + + + {# password field #} + {% bootstrap_button '' button_type='button' icon='eye-open' button_class='btn-default' id='passwdtoggler' size='sm' as bt %} + {% bootstrap_field form.password form_group_class='form-group-sm' addon_after_class='input-group-btn' addon_after=bt %} + + + + + {% bootstrap_field form.description form_group_class='form-group-sm' %}
-
+
{% trans 'Save' as tx %} - {% bootstrap_button tx button_type='submit' icon='floppy-disk' button_class='btn-primary' %} + {% if perms.abonapp.change_abon %} + {% bootstrap_button tx button_type='submit' icon='floppy-disk' button_class='btn-primary' %} + {% else %} + {% bootstrap_button tx button_type='button' icon='floppy-disk' button_class='btn-primary disabled' %} + {% endif %} {% if perms.taskapp.add_task %} - + + {% trans 'Add new task' %} + + {% else %} + + + {% trans 'Add new task' %} {% endif %} -
-
-
-
- {% if ip and perms.abonapp.can_ping %} - - Ping - + {% if form.ip_address.value %} + {% if perms.abonapp.can_ping %} + + Ping + + {% else %} + + Ping + + {% endif %} {% else %} {% trans 'No have ip' %} @@ -100,6 +113,10 @@ {% trans 'Send sms' %} + {% else %} + + {% trans 'Send sms' %} + {% endif %}
diff --git a/abonapp/templates/abonapp/viewAbon.html b/abonapp/templates/abonapp/viewAbon.html deleted file mode 100644 index 47f5869..0000000 --- a/abonapp/templates/abonapp/viewAbon.html +++ /dev/null @@ -1,78 +0,0 @@ -{% extends request.is_ajax|yesno:'nullcont.htm,abonapp/ext.htm' %} -{% load i18n %} -{% block content %} - -
-
- -
-
-

{% trans 'View the subscriber' %}

-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{% trans 'User group' %} - {{ abon.group|default:_('Not assigned') }} -
{% trans 'Is active' %} - {{ abon.is_active|yesno:_('yes,no') }} -
{% trans 'login' %}{{ abon.username|default:_('Not assigned') }}
{% trans 'fio' %}{{ abon.fio|default:_('Not assigned') }}
{% trans 'Telephone' %}{{ abon.telephone|default:_('Not assigned') }}
{% trans 'Street' %} - {{ abon.street|default:_('Not assigned') }} -
{% trans 'House' %}{{ abon.house|default:_('Not assigned') }}
{% trans 'Ip Address' %}{{ abon.ip_address|default:_('Not assigned') }}
{% trans 'Password' %}{{ passw }}
- {% if abon.description %}

{{ abon.description }}

{% endif %} -
-
- -
-
-
-
-

{% trans 'User flags' %}

-
-
- {% for user_icon in abon.get_flag_icons %} - - {% endfor %} -
-
-
-
- -{% endblock %} \ No newline at end of file diff --git a/abonapp/urls.py b/abonapp/urls.py index d63a6ad..dd9a893 100644 --- a/abonapp/urls.py +++ b/abonapp/urls.py @@ -5,7 +5,7 @@ from . import views app_name = 'abonapp' subscriber_patterns = [ - url(r'^$', views.abonhome, name='abon_home'), + url(r'^$', views.AbonHomeUpdateView.as_view(), name='abon_home'), url(r'^services/$', views.abon_services, name='abon_services'), url(r'^amount/$', views.abonamount, name='abon_amount'), url(r'^debts/$', views.DebtsListView.as_view(), name='abon_debts'), diff --git a/abonapp/views.py b/abonapp/views.py index 06dce8b..69e7a88 100644 --- a/abonapp/views.py +++ b/abonapp/views.py @@ -28,6 +28,7 @@ from statistics.models import getModel from group_app.models import Group from guardian.shortcuts import get_objects_for_user, assign_perm from guardian.decorators import permission_required_or_403 as permission_required +from djing import ping from djing.global_base_views import OrderingMixin, BaseListWithFiltering, SecureApiView PAGINATION_ITEMS_PER_PAGE = getattr(settings, 'PAGINATION_ITEMS_PER_PAGE', 10) @@ -267,66 +268,82 @@ def abon_services(request, gid, uname): }) -@login_required -@mydefs.only_admins -def abonhome(request, gid, uname): - abon = get_object_or_404(models.Abon, username=uname) - group = get_object_or_404(Group, pk=gid) - if not request.user.has_perm('group_app.can_view_group', group): - raise PermissionDenied - frm = None - passw = None - try: - if request.method == 'POST': - if not request.user.has_perm('abonapp.change_abon'): - raise PermissionDenied - frm = forms.AbonForm(request.POST, instance=abon) - if frm.is_valid(): - newip = request.POST.get('ip') - if newip: - abon.ip_address = newip - abon = frm.save() - res = abon.sync_with_nas(created=False) - if isinstance(res, Exception): - messages.warning(request, res) - messages.success(request, _('edit abon success msg')) - else: - messages.warning(request, _('fix form errors')) - else: - passw = models.AbonRawPassword.objects.get(account=abon).passw_text - frm = forms.AbonForm(instance=abon, initial={'password': passw}) - if abon.device is None: - messages.warning(request, _('User device was not found')) - except mydefs.LogicError as e: - messages.error(request, e) +@method_decorator([login_required, mydefs.only_admins], name='dispatch') +@method_decorator(permission_required('abonapp.change_abon'), name='post') +class AbonHomeUpdateView(UpdateView): + 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 mydefs.LogicError as e: + messages.error(request, e) + except (NasFailedResult, NasNetworkError) as e: + messages.error(request, e) + except models.AbonRawPassword.DoesNotExist: + messages.warning(request, _('User has not have password, and cannot login')) + except mydefs.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.can_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 + res = abon.sync_with_nas(created=False) + if isinstance(res, Exception): + messages.warning(self.request, res) + 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 passw = models.AbonRawPassword.objects.get(account=abon).passw_text - frm = forms.AbonForm(instance=abon, initial={'password': passw}) + return { + 'password': passw + } - except (NasFailedResult, NasNetworkError) as e: - messages.error(request, e) - except models.AbonRawPassword.DoesNotExist: - messages.warning(request, _('User has not have password, and cannot login')) - except mydefs.MultipleException as errs: - for err in errs.err_list: - messages.error(request, err) + def get_context_data(self, **kwargs): + abon = self.object + dev = getattr(abon, 'device') + context = { + 'group': self.group, + 'device': dev, + 'dev_ports': DevPort.objects.filter(device=dev) if dev else None + } + context.update(kwargs) + return super(AbonHomeUpdateView, self).get_context_data(**context) - if request.user.has_perm('abonapp.change_abon'): - return render(request, 'abonapp/editAbon.html', { - 'form': frm or forms.AbonForm(instance=abon, initial={'password': passw}), - 'abon': abon, - 'group': group, - 'ip': abon.ip_address, - 'is_bad_ip': getattr(abon, 'is_bad_ip', False), - 'device': abon.device, - 'dev_ports': DevPort.objects.filter(device=abon.device) if abon.device else None - }) - else: - return render(request, 'abonapp/viewAbon.html', { - 'abon': abon, - 'group': group, - 'ip': abon.ip_address, - 'passw': passw - }) + def get_success_url(self): + abon = self.object + return resolve_url('abonapp:abon_home', + gid=getattr(abon.group, 'pk', 0), + uname=abon.username + ) @transaction.atomic @@ -698,7 +715,7 @@ def abon_ping(request): tm = Transmitter() ping_result = tm.ping(ip) if ping_result is None: - if mydefs.ping(ip, 10): + if ping(ip, 10): status = True text = ' %s' % _('ping ok') else: @@ -792,9 +809,9 @@ def save_user_dev_port(request, gid, uname): 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() - }) + 'user_url': user_url, + 'user_name': other_abon.get_full_name() + }) return redirect('abonapp:abon_home', gid, uname) except models.Abon.DoesNotExist: pass diff --git a/accounts_app/templates/accounts/create_acc.html b/accounts_app/templates/accounts/create_acc.html index b9987b4..34d396f 100644 --- a/accounts_app/templates/accounts/create_acc.html +++ b/accounts_app/templates/accounts/create_acc.html @@ -1,4 +1,5 @@ {% extends request.is_ajax|yesno:'bajax.html,base.html' %} +{% load globaltags %} {% load i18n %} {% block main %} @@ -62,7 +63,7 @@
-
diff --git a/accounts_app/templates/accounts/settings/ch_info.html b/accounts_app/templates/accounts/settings/ch_info.html index df563c0..8d38752 100644 --- a/accounts_app/templates/accounts/settings/ch_info.html +++ b/accounts_app/templates/accounts/settings/ch_info.html @@ -1,4 +1,5 @@ {% extends request.is_ajax|yesno:'nullcont.htm,accounts/settings/ext.htm' %} +{% load globaltags %} {% load i18n %} {% block content %} @@ -38,7 +39,7 @@
-
diff --git a/djing/settings.py b/djing/settings.py index 65f3a67..c5a3629 100644 --- a/djing/settings.py +++ b/djing/settings.py @@ -172,7 +172,7 @@ DEFAULT_SNMP_PASSWORD = local_settings.DEFAULT_SNMP_PASSWORD TELEGRAM_BOT_TOKEN = local_settings.TELEGRAM_BOT_TOKEN -TELEPHONE_REGEXP = r'^\+[7,8,9,3]\d{10,11}$' +TELEPHONE_REGEXP = local_settings.TELEPHONE_REGEXP ASTERISK_MANAGER_AUTH = local_settings.ASTERISK_MANAGER_AUTH @@ -184,3 +184,13 @@ API_AUTH_SUBNET = local_settings.API_AUTH_SUBNET # Company name COMPANY_NAME = local_settings.COMPANY_NAME + + +# bootstrap3 settings +BOOTSTRAP3 = { + # Label class to use in horizontal forms + 'horizontal_label_class': 'col-md-3', + + # Field class to use in horizontal forms + 'horizontal_field_class': 'col-md-9', +} \ No newline at end of file