From 64a93d7e479c2a5eb6b2756852dd528940c91198 Mon Sep 17 00:00:00 2001 From: www-data Date: Sat, 14 Apr 2018 16:27:55 +0300 Subject: [PATCH 1/8] fix ping import --- abonapp/views.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/abonapp/views.py b/abonapp/views.py index e89de0d..8d79e10 100644 --- a/abonapp/views.py +++ b/abonapp/views.py @@ -29,6 +29,7 @@ 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.global_base_views import OrderingMixin, BaseListWithFiltering, HashAuthView, AllowedSubnetMixin +from djing import ping PAGINATION_ITEMS_PER_PAGE = getattr(settings, 'PAGINATION_ITEMS_PER_PAGE', 10) @@ -698,7 +699,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: From c4bf0f37805d7b3f139225328f59529307c18edd Mon Sep 17 00:00:00 2001 From: www-data Date: Sat, 21 Apr 2018 11:11:56 +0300 Subject: [PATCH 2/8] Fix when tariff is none --- abonapp/models.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/abonapp/models.py b/abonapp/models.py index f2840b1..6a5db6b 100644 --- a/abonapp/models.py +++ b/abonapp/models.py @@ -263,6 +263,8 @@ 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)) From 111bf2700cc12aeb6fa7fb639d7ae2e8100a4f3d Mon Sep 17 00:00:00 2001 From: Dmitry Date: Sun, 22 Apr 2018 11:31:31 +0000 Subject: [PATCH 3/8] Refactoring abonhome view, renamed to AbonHomeUpdateView --- abonapp/forms.py | 2 +- abonapp/urls.py | 2 +- abonapp/views.py | 131 ++++++++++++++++++++++++++--------------------- 3 files changed, 76 insertions(+), 59 deletions(-) diff --git a/abonapp/forms.py b/abonapp/forms.py index 4c1bdbe..5d1e64c 100644 --- a/abonapp/forms.py +++ b/abonapp/forms.py @@ -58,7 +58,7 @@ class AbonForm(forms.ModelForm): 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/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 8bb161c..fd3551f 100644 --- a/abonapp/views.py +++ b/abonapp/views.py @@ -268,66 +268,83 @@ 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) - passw = models.AbonRawPassword.objects.get(account=abon).passw_text - frm = forms.AbonForm(instance=abon, initial={'password': passw}) +@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 - 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 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 - 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, + def get_initial(self): + abon = self.object + passw = models.AbonRawPassword.objects.get(account=abon).passw_text + return { + 'password': passw + } + + def get_context_data(self, **kwargs): + abon = self.object + dev = getattr(abon, 'device') + context = { + 'group': self.group, '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 - }) + 'device': dev, + 'dev_ports': DevPort.objects.filter(device=dev) if dev else None + } + context.update(kwargs) + return super(AbonHomeUpdateView, self).get_context_data(**context) + + 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 From 932a2a636e5fe58a2cf3f3de9432d07cddd514ad Mon Sep 17 00:00:00 2001 From: Dmitry Date: Sun, 22 Apr 2018 21:51:43 +0000 Subject: [PATCH 4/8] place telephone regep from global settings --- accounts_app/templates/accounts/create_acc.html | 3 ++- accounts_app/templates/accounts/settings/ch_info.html | 3 ++- djing/settings.py | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) 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..bb38d4e 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 From b1f6ce7552978074b5e4fc9ba5802743cb0dfa05 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Sun, 22 Apr 2018 21:52:49 +0000 Subject: [PATCH 5/8] Add ip address existing validate --- abonapp/forms.py | 6 +++++- abonapp/locale/ru/LC_MESSAGES/django.po | 4 ---- abonapp/models.py | 9 ++++----- abonapp/views.py | 7 +++---- 4 files changed, 12 insertions(+), 14 deletions(-) diff --git a/abonapp/forms.py b/abonapp/forms.py index 5d1e64c..dbadb65 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}$') @@ -56,6 +56,10 @@ class AbonForm(forms.ModelForm): 'class': 'form-control', 'type': 'password', 'autocomplete': 'new-password' })) + ip_address = forms.CharField(widget=forms.TextInput(attrs={ + 'pattern': IP_ADDR_REGEX + }), label=_('Ip Address')) + class Meta: model = models.Abon fields = ['username', 'telephone', 'fio', 'group', 'description', 'street', 'house', 'is_active', 'ip_address'] 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 6a5db6b..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) @@ -269,13 +269,12 @@ class Abon(BaseAccount): 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/views.py b/abonapp/views.py index fd3551f..69e7a88 100644 --- a/abonapp/views.py +++ b/abonapp/views.py @@ -332,7 +332,6 @@ class AbonHomeUpdateView(UpdateView): dev = getattr(abon, 'device') context = { 'group': self.group, - 'is_bad_ip': getattr(abon, 'is_bad_ip', False), 'device': dev, 'dev_ports': DevPort.objects.filter(device=dev) if dev else None } @@ -810,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 From ceea3e45a620b2c3baeb30b946c3344e81c4da59 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Sun, 22 Apr 2018 22:18:07 +0000 Subject: [PATCH 6/8] refactoring ip_address field, and make one template for abon viewing --- abonapp/forms.py | 7 ++- abonapp/templates/abonapp/editAbon.html | 65 ++++++++++++++------- abonapp/templates/abonapp/viewAbon.html | 78 ------------------------- 3 files changed, 50 insertions(+), 100 deletions(-) delete mode 100644 abonapp/templates/abonapp/viewAbon.html diff --git a/abonapp/forms.py b/abonapp/forms.py index dbadb65..6f26e20 100644 --- a/abonapp/forms.py +++ b/abonapp/forms.py @@ -36,14 +36,17 @@ 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={ diff --git a/abonapp/templates/abonapp/editAbon.html b/abonapp/templates/abonapp/editAbon.html index 1524d4c..993f6de 100644 --- a/abonapp/templates/abonapp/editAbon.html +++ b/abonapp/templates/abonapp/editAbon.html @@ -10,7 +10,12 @@

{% trans 'Change subscriber' %}

-
{% csrf_token %} + + {% if perms.abonapp.change_abon %} + {% url 'abonapp:abon_home' group.pk abon.username as ahlink %} + {% endif %} + + {% 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' %} @@ -35,19 +40,19 @@
-
- -
-
- - - - - - -
-
-
+ {# 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 label_class='col-sm-4' field_class='col-sm-8' form_group_class='form-group-sm' addon_after_class='input-group-btn' addon_after=bt %} + + {% 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' %} @@ -74,11 +79,21 @@
{% 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 %} - + + + + {% else %} + + + {% endif %}
@@ -86,10 +101,16 @@
- {% 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 +121,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 From f553510a59b12412b4abc59487d3f0fb9378ff54 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Sun, 22 Apr 2018 23:39:22 +0000 Subject: [PATCH 7/8] Refactoring abon view template to bootstrap3 lib --- abonapp/forms.py | 6 +- abonapp/templates/abonapp/editAbon.html | 90 +++++++++++-------------- djing/settings.py | 10 +++ 3 files changed, 54 insertions(+), 52 deletions(-) diff --git a/abonapp/forms.py b/abonapp/forms.py index 6f26e20..f3e5bd1 100644 --- a/abonapp/forms.py +++ b/abonapp/forms.py @@ -53,11 +53,11 @@ class AbonForm(forms.ModelForm): '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 diff --git a/abonapp/templates/abonapp/editAbon.html b/abonapp/templates/abonapp/editAbon.html index 993f6de..23f789f 100644 --- a/abonapp/templates/abonapp/editAbon.html +++ b/abonapp/templates/abonapp/editAbon.html @@ -17,66 +17,62 @@ {% 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' %} + {% bootstrap_field form.username form_group_class='form-group-sm' %} + {% bootstrap_field form.fio form_group_class='form-group-sm' %} + + + {# 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 %} + + {% 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 %} -
- -
-
- {{ form.telephone }}{{ form.telephone.errors }} - - - - - - - - - - - -
-
-
{# 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 label_class='col-sm-4' field_class='col-sm-8' form_group_class='form-group-sm' addon_after_class='input-group-btn' addon_after=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.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 }} - - - -
-
-
- - {% bootstrap_field form.description label_class='col-sm-4' field_class='col-sm-8' form_group_class='form-group-sm' %} + {% bootstrap_field form.description form_group_class='form-group-sm' %}