diff --git a/abonapp/locale/ru/LC_MESSAGES/django.po b/abonapp/locale/ru/LC_MESSAGES/django.po
index 35afe9e..ac9e7a6 100644
--- a/abonapp/locale/ru/LC_MESSAGES/django.po
+++ b/abonapp/locale/ru/LC_MESSAGES/django.po
@@ -389,10 +389,6 @@ msgstr "Дом"
msgid "Is active"
msgstr "Активен"
-#: abonapp/templates/abonapp/editAbon.html:106
-msgid "Send account info to user"
-msgstr "Отправить данные абоненту"
-
#: abonapp/templates/abonapp/editAbon.html:109
#: abonapp/templates/abonapp/editAbon.html:110
#, fuzzy
@@ -473,7 +469,7 @@ msgstr "Принадлежность услуг к группам"
#: abonapp/templates/abonapp/invoiceForPayment.html:10
#: abonapp/templates/abonapp/payHistory.html:39
msgid "Debts"
-msgstr "Долги"
+msgstr "Квитанции (долги)"
#: abonapp/templates/abonapp/invoiceForPayment.html:15
msgid "Debtor"
@@ -606,10 +602,6 @@ msgstr "Балланс"
msgid "Subscribers not found"
msgstr "Абоненты не найдены"
-#: abonapp/templates/abonapp/peoples.html:130
-msgid "Refresh subscribers on NAS"
-msgstr "Обновить абонентов в NAS"
-
#: abonapp/templates/abonapp/peoples.html:133
msgid "Tariffs in groups"
msgstr "Тарифы в группах"
@@ -736,10 +728,6 @@ msgstr "Адрес"
msgid "delete abon success msg"
msgstr "Абонент успешно удалён"
-#: abonapp/views.py:180
-msgid "I not know what to delete"
-msgstr "Не понятно что удалять"
-
#: abonapp/views.py:184
#, python-format
msgid "NAS says: '%s'"
@@ -993,3 +981,27 @@ msgstr "Номер телефона успешно удалён"
msgid "Telephone not found"
msgstr "Телефон не найден"
+
+msgid "Can view subscriber group"
+msgstr "Может просматривать группу абонентов"
+
+msgid "Permission denied"
+msgstr "Доступ запрещён"
+
+msgid "Can view subscriber logs"
+msgstr "Может видеть логи абонента"
+
+msgid "Can view invoice for payment"
+msgstr "Может видеть назначенные платежи"
+
+msgid "Debt"
+msgstr "Квитанция (долг)"
+
+msgid "Passport Info"
+msgstr "Паспортные данные"
+
+msgid "Can ping"
+msgstr "Может пинговать"
+
+msgid "Can view additional telephones"
+msgstr "Может видеть дополнительные телефоны"
diff --git a/abonapp/migrations/0001_squashed_0022_auto_20170816_1109.py b/abonapp/migrations/0001_squashed_0022_auto_20170816_1109.py
index b68d5ef..89e222c 100644
--- a/abonapp/migrations/0001_squashed_0022_auto_20170816_1109.py
+++ b/abonapp/migrations/0001_squashed_0022_auto_20170816_1109.py
@@ -231,11 +231,6 @@ class Migration(migrations.Migration):
name='tariffs',
field=models.ManyToManyField(blank=True, related_name='tariff_groups', to='tariff_app.Tariff'),
),
- migrations.AddField(
- model_name='abon',
- name='opt82',
- field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='abonapp.Opt82'),
- ),
migrations.CreateModel(
name='PassportInfo',
fields=[
@@ -247,11 +242,6 @@ class Migration(migrations.Migration):
('abon', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='abonapp.Abon')),
],
),
- migrations.AlterField(
- model_name='abon',
- name='opt82',
- field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='abonapp.Opt82'),
- ),
migrations.CreateModel(
name='AbonDevice',
fields=[
@@ -282,10 +272,6 @@ class Migration(migrations.Migration):
name='ip_address',
field=mydefs.MyGenericIPAddressField(blank=True, max_length=8, null=True, protocol='ipv4'),
),
- migrations.RemoveField(
- model_name='abon',
- name='opt82',
- ),
migrations.AddField(
model_name='abon',
name='dev_port',
diff --git a/abonapp/migrations/0003_auto_20170927_1838.py b/abonapp/migrations/0003_auto_20170927_1838.py
new file mode 100644
index 0000000..f15a614
--- /dev/null
+++ b/abonapp/migrations/0003_auto_20170927_1838.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9 on 2017-09-27 18:38
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('abonapp', '0002_auto_20170905_1248'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='abon',
+ options={'permissions': (('can_buy_tariff', 'Покупка тарифа абоненту'), ('can_view_passport', 'Может просматривать паспортные данные'), ('can_add_ballance', 'Пополнение счёта'), ('can_ping', 'Может пинговать')), 'verbose_name': 'Абонент', 'verbose_name_plural': 'Абоненты'},
+ ),
+ migrations.AlterModelOptions(
+ name='abongroup',
+ options={'permissions': (('can_view_abongroup', 'Может просматривать группу абонентов'),), 'verbose_name': 'Группа абонентов', 'verbose_name_plural': 'Группы абонентов'},
+ ),
+ migrations.AlterModelOptions(
+ name='abonlog',
+ options={'permissions': (('can_view_abonlog', 'Может видеть логи абонента'),)},
+ ),
+ migrations.AlterModelOptions(
+ name='additionaltelephone',
+ options={'ordering': ('owner_name',), 'permissions': (('can_view_additionaltelephones', 'Может видеть дополнительные телефоны'),), 'verbose_name': 'Дополнительный телефон', 'verbose_name_plural': 'Дополнительные телефоны'},
+ ),
+ migrations.AlterModelOptions(
+ name='invoiceforpayment',
+ options={'ordering': ('date_create',), 'permissions': (('can_view_invoiceforpayment', 'Может видеть назначенные платежи'),), 'verbose_name': 'Квитанция (долг)', 'verbose_name_plural': 'Квитанции (долги)'},
+ ),
+ migrations.AlterModelOptions(
+ name='passportinfo',
+ options={'verbose_name': 'Паспортные данные', 'verbose_name_plural': 'Паспортные данные'},
+ ),
+ migrations.AlterModelTable(
+ name='passportinfo',
+ table='passport_info',
+ ),
+ ]
diff --git a/abonapp/models.py b/abonapp/models.py
index d492f82..212ae5c 100644
--- a/abonapp/models.py
+++ b/abonapp/models.py
@@ -1,13 +1,15 @@
# -*- coding: utf-8 -*-
from django.core.exceptions import ValidationError
from django.core.validators import RegexValidator
+from django.db.models.signals import post_save, post_delete, pre_delete, post_init
+from django.dispatch import receiver
from django.utils import timezone
from django.db import models
from django.core import validators
from django.utils.translation import ugettext as _
from agent import Transmitter, AbonStruct, TariffStruct, NasFailedResult, NasNetworkError
from tariff_app.models import Tariff
-from accounts_app.models import UserProfile
+from accounts_app.models import UserProfile, MyUserManager
from mydefs import MyGenericIPAddressField, ip2int, LogicError, ip_addr_regex
from django.conf import settings
@@ -23,7 +25,7 @@ class AbonGroup(models.Model):
class Meta:
db_table = 'abonent_groups'
permissions = (
- ('can_add_ballance', _('fill account')),
+ ('can_view_abongroup', _('Can view subscriber group')),
)
verbose_name = _('Abon group')
verbose_name_plural = _('Abon groups')
@@ -41,6 +43,9 @@ class AbonLog(models.Model):
class Meta:
db_table = 'abonent_log'
+ permissions = (
+ ('can_view_abonlog', _('Can view subscriber logs')),
+ )
def __str__(self):
return self.comment
@@ -133,6 +138,13 @@ class ExtraFieldsModel(models.Model):
db_table = 'abon_extra_fields'
+
+class AbonManager(MyUserManager):
+
+ def get_queryset(self):
+ return super(MyUserManager, self).get_queryset().filter(is_admin=False)
+
+
class Abon(UserProfile):
current_tariff = models.ForeignKey(AbonTariff, null=True, blank=True, on_delete=models.SET_NULL)
group = models.ForeignKey(AbonGroup, models.SET_NULL, blank=True, null=True)
@@ -150,11 +162,15 @@ class Abon(UserProfile):
def active_tariff(self):
return self.current_tariff
+ objects = AbonManager()
+
class Meta:
db_table = 'abonent'
permissions = (
('can_buy_tariff', _('Buy service perm')),
- ('can_view_passport', _('Can view passport'))
+ ('can_view_passport', _('Can view passport')),
+ ('can_add_ballance', _('fill account')),
+ ('can_ping', _('Can ping'))
)
verbose_name = _('Abon')
verbose_name_plural = _('Abons')
@@ -261,6 +277,11 @@ class PassportInfo(models.Model):
date_of_acceptance = models.DateField()
abon = models.OneToOneField(Abon, on_delete=models.SET_NULL, blank=True, null=True)
+ class Meta:
+ db_table = 'passport_info'
+ verbose_name = _('Passport Info')
+ verbose_name_plural = _('Passport Info')
+
def __str__(self):
return "%s %s" % (self.series, self.number)
@@ -287,6 +308,11 @@ class InvoiceForPayment(models.Model):
class Meta:
ordering = ('date_create',)
db_table = 'abonent_inv_pay'
+ permissions = (
+ ('can_view_invoiceforpayment', _('Can view invoice for payment')),
+ )
+ verbose_name = _('Debt')
+ verbose_name_plural = _('Debts')
# Log for pay system "AllTime"
@@ -345,11 +371,16 @@ class AdditionalTelephone(models.Model):
class Meta:
db_table = 'additional_telephones'
ordering = ('owner_name',)
+ permissions = (
+ ('can_view_additionaltelephones', _('Can view additional telephones')),
+ )
verbose_name = _('Additional telephone')
verbose_name_plural = _('Additional telephones')
-def abon_post_save(sender, instance, **kwargs):
+@receiver(post_save, sender=Abon)
+def abon_post_save(sender, **kwargs):
+ instance, created = kwargs["instance"], kwargs["created"]
timeout = None
if hasattr(instance, 'is_dhcp') and instance.is_dhcp:
timeout = getattr(settings, 'DHCP_TIMEOUT', 14400)
@@ -358,7 +389,7 @@ def abon_post_save(sender, instance, **kwargs):
return True
try:
tm = Transmitter()
- if kwargs['created']:
+ if created:
# создаём абонента
tm.add_user(agent_abon, ip_timeout=timeout)
else:
@@ -370,9 +401,11 @@ def abon_post_save(sender, instance, **kwargs):
return True
-def abon_del_signal(sender, instance, **kwargs):
+@receiver(post_delete, sender=Abon)
+def abon_del_signal(sender, **kwargs):
+ abon = kwargs["instance"]
try:
- ab = instance.build_agent_struct()
+ ab = abon.build_agent_struct()
if ab is None:
return True
# подключаемся к NAS'у
@@ -383,17 +416,21 @@ def abon_del_signal(sender, instance, **kwargs):
return True
-def abon_tariff_post_init(sender, instance, **kwargs):
- if getattr(instance, 'time_start') is None:
- instance.time_start = timezone.now()
- calc_obj = instance.tariff.get_calc_type()(instance)
- if getattr(instance, 'deadline') is None:
- instance.deadline = calc_obj.calc_deadline()
+@receiver(post_init, sender=AbonTariff)
+def abon_tariff_post_init(sender, **kwargs):
+ abon_tariff = kwargs["instance"]
+ if getattr(abon_tariff, 'time_start') is None:
+ abon_tariff.time_start = timezone.now()
+ calc_obj = abon_tariff.tariff.get_calc_type()(abon_tariff)
+ if getattr(abon_tariff, 'deadline') is None:
+ abon_tariff.deadline = calc_obj.calc_deadline()
-def abontariff_pre_delete(sender, instance, **kwargs):
+@receiver(pre_delete, sender=AbonTariff)
+def abontariff_pre_delete(sender, **kwargs):
+ abon_tariff = kwargs["instance"]
try:
- abon = Abon.objects.get(current_tariff=instance)
+ abon = Abon.objects.get(current_tariff=abon_tariff)
ab = abon.build_agent_struct()
if ab is None:
return True
@@ -404,9 +441,3 @@ def abontariff_pre_delete(sender, instance, **kwargs):
except (NasFailedResult, NasNetworkError, ConnectionResetError) as e:
print('NetErr:', e)
return True
-
-
-models.signals.post_save.connect(abon_post_save, sender=Abon)
-models.signals.post_delete.connect(abon_del_signal, sender=Abon)
-models.signals.post_init.connect(abon_tariff_post_init, sender=AbonTariff)
-models.signals.pre_delete.connect(abontariff_pre_delete, sender=AbonTariff)
diff --git a/abonapp/templates/abonapp/complete_service.html b/abonapp/templates/abonapp/complete_service.html
deleted file mode 100644
index d0939a2..0000000
--- a/abonapp/templates/abonapp/complete_service.html
+++ /dev/null
@@ -1,58 +0,0 @@
-{% extends request.is_ajax|yesno:'bajax.html,base.html' %}
-{% load i18n %}
-{% block main %}
-
-
-
- - {% trans 'User groups' %}
- - {{ abon_group.title }}
- - {{ abon.fio }}
- - {% trans 'Finish service' %}
-
-
- {% include 'message_block.html' %}
-
-
-
-
{% trans 'Finish service' %}
-
-
-
-
-{% endblock %}
\ No newline at end of file
diff --git a/abonapp/templates/abonapp/editAbon.html b/abonapp/templates/abonapp/editAbon.html
index 4cf8744..5e93ebd 100644
--- a/abonapp/templates/abonapp/editAbon.html
+++ b/abonapp/templates/abonapp/editAbon.html
@@ -1,5 +1,6 @@
{% extends request.is_ajax|yesno:'nullcont.htm,abonapp/ext.htm' %}
{% load i18n %}
+{% load guardian_tags %}
{% block content %}
- {% if ip %}
+ {% if ip and perms.abonapp.can_ping %}
+ {% if perms.abonapp.change_abon %}
{% trans 'Select the device' %}
@@ -146,7 +145,7 @@
+ {% endif %}
+ {% if perms.abonapp.add_extrafieldsmodel %}
{% trans 'Extra fields' %}
@@ -242,6 +243,7 @@
+ {% endif %}
diff --git a/abonapp/templates/abonapp/group_list.html b/abonapp/templates/abonapp/group_list.html
index 59fb7ed..d3518c9 100644
--- a/abonapp/templates/abonapp/group_list.html
+++ b/abonapp/templates/abonapp/group_list.html
@@ -38,7 +38,7 @@
{% if perms.abonapp.delete_abongroup %}
{% if gr.usercount == 0 %}
-
+
{% endif %}
diff --git a/abonapp/templates/abonapp/payHistory.html b/abonapp/templates/abonapp/payHistory.html
index f81a128..a6be2e6 100644
--- a/abonapp/templates/abonapp/payHistory.html
+++ b/abonapp/templates/abonapp/payHistory.html
@@ -1,5 +1,6 @@
{% extends request.is_ajax|yesno:'nullcont.htm,abonapp/ext.htm' %}
{% load i18n %}
+{% load guardian_tags %}
{% block content %}
@@ -30,12 +31,17 @@
|
- {% if perms.abonapp.can_add_ballance %}
-
- {% trans 'Fill account' %}
-
+ {% get_obj_perms request.user for abon as 'fill_perm' %}
+ {% if 'abonapp.can_add_ballance' in fill_perm %}
+
+ {% trans 'Fill account' %}
+
+ {% else %}
+
+ {% trans 'Fill account' %}
+
{% endif %}
-
+
{% trans 'Debts' %}
|
diff --git a/abonapp/templates/abonapp/peoples.html b/abonapp/templates/abonapp/peoples.html
index 905ea2e..9e787fe 100644
--- a/abonapp/templates/abonapp/peoples.html
+++ b/abonapp/templates/abonapp/peoples.html
@@ -104,7 +104,7 @@
{{ human.ballance }} |
{% if can_del_trf %}
-
+
{% endif %}
@@ -126,14 +126,11 @@
|
|
{% if perms.abonapp.add_abon %}
-
+
{% trans 'Add abon' %}
{% endif %}
-
- {% trans 'Refresh subscribers on NAS' %}
-
-
+
{% trans 'Tariffs in groups' %}
|
diff --git a/abonapp/templates/abonapp/viewAbon.html b/abonapp/templates/abonapp/viewAbon.html
index 914b7e9..b05df1a 100644
--- a/abonapp/templates/abonapp/viewAbon.html
+++ b/abonapp/templates/abonapp/viewAbon.html
@@ -61,6 +61,7 @@
+ {% if perms.abonapp.can_view_passport %}
+ {% endif %}
{% endblock %}
\ No newline at end of file
diff --git a/abonapp/urls.py b/abonapp/urls.py
index 3aa6686..048f525 100644
--- a/abonapp/urls.py
+++ b/abonapp/urls.py
@@ -13,7 +13,7 @@ urlpatterns = [
url(r'^log$', views.log_page, name='log'),
- url(r'^del$', views.delentity, name='del_abon'),
+ url(r'^del$', views.del_abon, name='del_abon'),
url(r'^pay$', views.terminal_pay, name='terminal_pay'),
@@ -21,8 +21,6 @@ urlpatterns = [
url(r'^ping$', views.abon_ping, name='ping'),
- url(r'^refresh_group_nas(?P\d+)$', views.update_nas, name='update_nas'),
-
# Api's
url(r'^api/abons$', views.abons),
url(r'^api/abon_filter$', views.search_abon)
diff --git a/abonapp/urls_abon.py b/abonapp/urls_abon.py
index 9267cb1..d37a86c 100644
--- a/abonapp/urls_abon.py
+++ b/abonapp/urls_abon.py
@@ -19,7 +19,6 @@ urlpatterns = [
url(r'^(?P\d+)/addinvoice$', views.add_invoice, name='add_invoice'),
url(r'^(?P\d+)/pick$', views.pick_tariff, name='pick_tariff'),
url(r'^(?P\d+)/passport_view$', views.passport_view, name='passport_view'),
- url(r'^(?P\d+)/complete_service(?P\d+)$', views.complete_service, name='compl_srv'),
url(r'^(?P\d+)/chart$', views.charts, name='charts'),
url(r'^(?P\d+)/dials$', views.dials, name='dials'),
url(r'^(?P\d+)/extra_field$', views.make_extra_field, name='extra_field'),
diff --git a/abonapp/views.py b/abonapp/views.py
index 92a62fc..772083f 100644
--- a/abonapp/views.py
+++ b/abonapp/views.py
@@ -6,8 +6,7 @@ from django.db import IntegrityError, ProgrammingError
from django.db.models import Count, Q
from django.db.transaction import atomic
from django.shortcuts import render, redirect, get_object_or_404, resolve_url
-from django.contrib.auth.decorators import login_required, permission_required
-from django.utils import timezone
+from django.contrib.auth.decorators import login_required
from django.http import HttpResponse
from django.contrib import messages
from django.utils.translation import ugettext_lazy as _
@@ -23,17 +22,22 @@ from datetime import datetime, date
from taskapp.models import Task
from dialing_app.models import AsteriskCDR
from statistics.models import getModel, get_dates
+from guardian.shortcuts import get_objects_for_user, assign_perm
+from guardian.decorators import permission_required_or_403 as permission_required
@login_required
@mydefs.only_admins
def peoples(request, gid):
+ abon_group = get_object_or_404(models.AbonGroup, pk=gid)
+ if not request.user.has_perm('abonapp.can_view_abongroup', abon_group):
+ raise PermissionDenied
street_id = mydefs.safe_int(request.GET.get('street'))
peoples_list = models.Abon.objects.select_related('group', 'street')
if street_id > 0:
- peoples_list = peoples_list.filter(group=gid, street=street_id)
+ peoples_list = peoples_list.filter(group=abon_group, street=street_id)
else:
- peoples_list = peoples_list.filter(group=gid)
+ peoples_list = peoples_list.filter(group=abon_group)
# фильтр
dr, field = mydefs.order_helper(request)
@@ -72,7 +76,10 @@ def addgroup(request):
if request.method == 'POST':
frm = forms.AbonGroupForm(request.POST)
if frm.is_valid():
- frm.save()
+ grp = frm.save()
+ assign_perm('abonapp.can_view_abongroup', request.user, grp)
+ assign_perm('abonapp.delete_abongroup', request.user, grp)
+ assign_perm('abonapp.change_abongroup', request.user, grp)
messages.success(request, _('create group success msg'))
return redirect('abonapp:group_list')
else:
@@ -91,6 +98,7 @@ def addgroup(request):
@mydefs.only_admins
def grouplist(request):
groups = models.AbonGroup.objects.annotate(usercount=Count('abon')).order_by('title')
+ groups = get_objects_for_user(request.user, 'abonapp.can_view_abongroup', klass=groups, accept_global_perms=False)
# фильтр
directory, field = mydefs.order_helper(request)
@@ -107,11 +115,13 @@ def grouplist(request):
@login_required
-@permission_required('abonapp.delete_abongroup')
def delgroup(request):
try:
agd = mydefs.safe_int(request.GET.get('id'))
- get_object_or_404(models.AbonGroup, pk=agd).delete()
+ abon_group = models.AbonGroup.objects.get(pk=agd)
+ if not request.user.has_perm('abonapp.delete_abongroup', abon_group):
+ raise PermissionDenied
+ abon_group.delete()
messages.success(request, _('delete group success msg'))
return mydefs.res_success(request, 'abonapp:group_list')
except (NasFailedResult, NasNetworkError) as e:
@@ -119,6 +129,8 @@ def delgroup(request):
except mydefs.MultipleException as errs:
for err in errs.err_list:
messages.add_message(request, messages.constants.ERROR, err)
+ except models.AbonGroup.DoesNotExist:
+ return mydefs.res_error(request, 'Group with id=%d does not exist' % agd)
return mydefs.res_error(request, 'abonapp:group_list')
@@ -129,10 +141,17 @@ def addabon(request, gid):
group = None
try:
group = get_object_or_404(models.AbonGroup, pk=gid)
+ if not request.user.has_perm('abonapp.can_view_abongroup', group):
+ raise PermissionDenied
if request.method == 'POST':
frm = forms.AbonForm(request.POST, initial={'group': group})
if frm.is_valid():
abon = frm.save()
+ assign_perm("abonapp.change_abon", request.user, abon)
+ assign_perm("abonapp.delete_abon", request.user, abon)
+ assign_perm("abonapp.can_buy_tariff", request.user, abon)
+ assign_perm("abonapp.can_view_passport", request.user, abon)
+ assign_perm('abonapp.can_add_ballance', request.user, abon)
messages.success(request, _('create abon success msg'))
return redirect('abonapp:abon_home', group.id, abon.pk)
else:
@@ -159,26 +178,18 @@ def addabon(request, gid):
@login_required
@mydefs.only_admins
-def delentity(request):
- typ = request.GET.get('t')
+def del_abon(request):
uid = request.GET.get('id')
try:
- if typ == 'a':
- if not request.user.has_perm('abonapp.delete_abon'):
- raise PermissionDenied
- abon = get_object_or_404(models.Abon, pk=uid)
- gid = abon.group.id
- abon.delete()
- messages.success(request, _('delete abon success msg'))
- return mydefs.res_success(request, resolve_url('abonapp:people_list', gid=gid))
- elif typ == 'g':
- if not request.user.has_perm('abonapp.delete_abongroup'):
- raise PermissionDenied
- get_object_or_404(models.AbonGroup, pk=uid).delete()
- messages.success(request, _('delete group success msg'))
- return mydefs.res_success(request, 'abonapp:group_list')
- else:
- messages.warning(request, _('I not know what to delete'))
+ abon = get_object_or_404(models.Abon, pk=uid)
+ if not request.user.has_perm('abonapp.delete_abon') or not request.user.has_perm(
+ 'abonapp.can_view_abongroup', abon.group):
+ raise PermissionDenied
+ gid = abon.group.id
+ abon.delete()
+ messages.success(request, _('delete abon success msg'))
+ return mydefs.res_success(request, resolve_url('abonapp:people_list', gid=gid))
+
except NasNetworkError as e:
messages.error(request, e)
except NasFailedResult as e:
@@ -190,12 +201,13 @@ def delentity(request):
@login_required
-@permission_required('abonapp.can_add_ballance')
@atomic
def abonamount(request, gid, uid):
abon = get_object_or_404(models.Abon, pk=uid)
try:
if request.method == 'POST':
+ if not request.user.has_perm('abonapp.can_add_ballance', abon):
+ raise PermissionDenied
abonid = mydefs.safe_int(request.POST.get('abonid'))
if abonid == int(uid):
amnt = mydefs.safe_float(request.POST.get('amount'))
@@ -218,6 +230,7 @@ def abonamount(request, gid, uid):
@login_required
@mydefs.only_admins
+@permission_required('abonapp.can_view_abongroup', (models.AbonGroup, 'pk', 'gid'))
def invoice_for_payment(request, gid, uid):
abon = get_object_or_404(models.Abon, pk=uid)
invoices = models.InvoiceForPayment.objects.filter(abon=abon)
@@ -231,6 +244,7 @@ def invoice_for_payment(request, gid, uid):
@login_required
@mydefs.only_admins
+@permission_required('abonapp.can_view_abongroup', (models.AbonGroup, 'pk', 'gid'))
def pay_history(request, gid, uid):
abon = get_object_or_404(models.Abon, pk=uid)
pay_history = models.AbonLog.objects.filter(abon=abon).order_by('-id')
@@ -246,6 +260,8 @@ def pay_history(request, gid, uid):
@mydefs.only_admins
def abon_services(request, gid, uid):
grp = get_object_or_404(models.AbonGroup, pk=gid)
+ if not request.user.has_perm('abonapp.can_view_abongroup', grp):
+ raise PermissionDenied
abon = get_object_or_404(models.Abon, pk=uid)
return render(request, 'abonapp/service.html', {
@@ -265,7 +281,7 @@ def abonhome(request, gid, uid):
passw = None
try:
if request.method == 'POST':
- if not request.user.has_perm('abonapp.change_abon'):
+ if not request.user.has_perm('abonapp.change_abon', abon):
raise PermissionDenied
frm = forms.AbonForm(request.POST, instance=abon)
if frm.is_valid():
@@ -303,6 +319,8 @@ def abonhome(request, gid, uid):
'dev_ports': DevPort.objects.filter(device=abon.device) if abon.device else None
})
else:
+ if not request.user.has_perm('abonapp.can_view_abongroup', abon_group):
+ raise PermissionDenied
return render(request, 'abonapp/viewAbon.html', {
'abon': abon,
'abon_group': abon_group,
@@ -356,11 +374,12 @@ def add_invoice(request, gid, uid):
@login_required
-@permission_required('abonapp.can_buy_tariff')
@atomic
def pick_tariff(request, gid, uid):
grp = get_object_or_404(models.AbonGroup, pk=gid)
abon = get_object_or_404(models.Abon, pk=uid)
+ if not request.user.has_perm('abonapp.can_buy_tariff', abon):
+ raise PermissionDenied
tariffs = grp.tariffs.all()
try:
if request.method == 'POST':
@@ -395,70 +414,13 @@ def pick_tariff(request, gid, uid):
@login_required
-@permission_required('abonapp.can_complete_service')
-@atomic
-def complete_service(request, gid, uid, srvid):
- abtar = get_object_or_404(models.AbonTariff, pk=srvid)
- abon = abtar.abon
- # считаем не использованные ресурсы
- calc_obj = abtar.tariff.get_calc_type()(abtar)
- # получаем сколько использовано
- res_amount = calc_obj.calc_amount()
- cashback = abtar.tariff.amount - res_amount
-
- if abtar.abon.group is None:
- abon.group = get_object_or_404(models.AbonGroup, pk=gid)
- abon.save(update_fields=['group'])
- if int(abtar.abon.pk) != int(uid) or int(abtar.abon.group.pk) != int(gid):
- # если что-то написали в урле вручную, то вернём на путь истинный
- return redirect('abonapp:compl_srv', gid=abtar.abon.group.pk, uid=abtar.abon.pk, srvid=srvid)
- time_use = None
- try:
- if request.method == 'POST':
- # досрочно завершаем услугу
- if request.POST.get('finish_confirm') == 'yes':
- if cashback > 0.5:
- # возвращаем деньги, которые абонент не использовал
- abon.add_ballance(
- request.user,
- cashback,
- _('Refunds for unused resources')
- )
- abon.save(update_fields=['ballance'])
-
- # удаляем запись о текущей услуге.
- abtar.delete()
- messages.success(request, _('Service has been finished successfully'))
- return redirect('abonapp:abon_services', gid, uid)
- else:
- raise mydefs.LogicError(_('Not confirmed'))
-
- time_use = mydefs.RuTimedelta(timezone.now() - abtar.time_start)
-
- except (mydefs.LogicError, NasFailedResult) as e:
- messages.error(request, e)
- except NasNetworkError as e:
- messages.warning(request, e)
- return redirect('abonapp:abon_home', gid, uid)
- except mydefs.MultipleException as errs:
- for err in errs.err_list:
- messages.add_message(request, messages.constants.ERROR, err)
-
- return render(request, 'abonapp/complete_service.html', {
- 'abtar': abtar,
- 'abon': abon,
- 'time_use': time_use,
- 'abon_group': get_object_or_404(models.AbonGroup, pk=gid),
- 'tcost': round(res_amount, 4),
- 'cashback': round(cashback, 4)
- })
-
-
-@login_required
-@permission_required('abonapp.delete_abontariff')
+@mydefs.only_admins
def unsubscribe_service(request, gid, uid, abon_tariff_id):
try:
- get_object_or_404(models.AbonTariff, pk=int(abon_tariff_id)).delete()
+ abon_tariff = get_object_or_404(models.AbonTariff, pk=int(abon_tariff_id))
+ if not request.user.has_perm('abonapp.delete_abontariff', abon_tariff):
+ raise PermissionDenied
+ abon_tariff.delete()
messages.success(request, _('User has been detached from service'))
except NasFailedResult as e:
messages.error(request, e)
@@ -471,7 +433,7 @@ def unsubscribe_service(request, gid, uid, abon_tariff_id):
@login_required
-@mydefs.only_admins
+@permission_required('abonapp.can_view_abonlog')
def log_page(request):
logs = models.AbonLog.objects.all()
logs = mydefs.pag_mn(request, logs)
@@ -481,42 +443,17 @@ def log_page(request):
@login_required
-@mydefs.only_admins
+@permission_required('abonapp.can_view_invoiceforpayment')
def debtors(request):
- # peoples_list = models.Abon.objects.filter(invoiceforpayment__status=True)
- # peoples_list = mydefs.pag_mn(request, peoples_list)
invs = models.InvoiceForPayment.objects.filter(status=True)
invs = mydefs.pag_mn(request, invs)
return render(request, 'abonapp/debtors.html', {
- # 'peoples': peoples_list
'invoices': invs
})
@login_required
-@mydefs.only_admins
-def update_nas(request, group_id):
- users = models.Abon.objects.filter(group=group_id)
- try:
- tm = Transmitter()
- for usr in users:
- if not usr.ip_address:
- continue
- agent_abon = usr.build_agent_struct()
- if agent_abon is not None:
- tm.update_user(agent_abon)
- except NasFailedResult as e:
- messages.error(request, e)
- except NasNetworkError as e:
- messages.warning(request, e)
- except mydefs.MultipleException as errs:
- for err in errs.err_list:
- messages.add_message(request, messages.constants.ERROR, err)
- return redirect('abonapp:people_list', gid=group_id)
-
-
-@login_required
-@mydefs.only_admins
+@permission_required('abonapp.can_view_abongroup', (models.AbonGroup, 'pk', 'gid'))
def task_log(request, gid, uid):
abon = get_object_or_404(models.Abon, pk=uid)
tasks = Task.objects.filter(abon=abon)
@@ -532,6 +469,8 @@ def task_log(request, gid, uid):
def passport_view(request, gid, uid):
try:
abon = models.Abon.objects.get(pk=uid)
+ if not request.user.has_perm('abonapp.can_view_passport', abon):
+ raise PermissionDenied
if request.method == 'POST':
try:
passport_instance = models.PassportInfo.objects.get(abon=abon)
@@ -566,6 +505,8 @@ def passport_view(request, gid, uid):
@mydefs.only_admins
def chgroup_tariff(request, gid):
grp = get_object_or_404(models.AbonGroup, pk=gid)
+ if not request.user.has_perm('abonapp.change_abongroup', grp):
+ raise PermissionDenied
if request.method == 'POST':
tr = request.POST.getlist('tr')
grp.tariffs.clear()
@@ -579,7 +520,7 @@ def chgroup_tariff(request, gid):
@login_required
-@mydefs.only_admins
+@permission_required('abonapp.change_abon')
def dev(request, gid, uid):
abon_dev = None
try:
@@ -609,6 +550,8 @@ def dev(request, gid, uid):
def clear_dev(request, gid, uid):
try:
abon = models.Abon.objects.get(pk=uid)
+ if not request.user.has_perm('abonapp.change_abon', abon):
+ raise PermissionDenied
abon.device = None
abon.save(update_fields=['device'])
messages.success(request, _('Device has successfully unattached'))
@@ -619,7 +562,7 @@ def clear_dev(request, gid, uid):
@login_required
-@mydefs.only_admins
+@permission_required('abonapp.can_view_abongroup', (models.AbonGroup, 'pk', 'gid'))
def charts(request, gid, uid):
high = 100
@@ -673,7 +616,7 @@ def charts(request, gid, uid):
@login_required
-@permission_required('abonapp.add_extra_fields_model')
+@permission_required('abonapp.add_extrafieldsmodel')
def make_extra_field(request, gid, uid):
abon = get_object_or_404(models.Abon, pk=uid)
try:
@@ -733,6 +676,7 @@ def extra_field_delete(request, gid, uid, fid):
@login_required
+@permission_required('abonapp.can_ping')
def abon_ping(request):
ip = request.GET.get('cmd_param')
status = False
@@ -753,7 +697,8 @@ def abon_ping(request):
text = ' %s' % _('ok ping, %d/%d loses') % ping_result
status = True
else:
- text = ' %s' % _('no ping, %d/%d loses') % ping_result
+ text = ' %s' % _(
+ 'no ping, %d/%d loses') % ping_result
else:
text = ' %s' % _('ping ok') + ' ' + str(ping_result)
status = True
@@ -773,6 +718,8 @@ def abon_ping(request):
@mydefs.only_admins
def dials(request, gid, uid):
abon = get_object_or_404(models.Abon, pk=uid)
+ if not request.user.has_perm('abonapp.can_view_abongroup', abon.group):
+ raise PermissionDenied
if hasattr(abon.group, 'pk') and abon.group.pk != int(gid):
return redirect('abonapp:dials', abon.group.pk, abon.pk)
if abon.telephone is not None and abon.telephone != '':
@@ -804,6 +751,8 @@ def save_user_dev_port(request, gid, uid):
else:
port = DevPort.objects.get(pk=user_port)
abon = models.Abon.objects.get(pk=uid)
+ if not request.user.has_perm('abonapp.change_abon', abon):
+ raise PermissionDenied
abon.dev_port = port
if abon.is_dynamic_ip != is_dynamic_ip:
abon.is_dynamic_ip = is_dynamic_ip
@@ -842,7 +791,8 @@ def street_add(request, gid):
def street_edit(request, gid):
try:
if request.method == 'POST':
- streets_pairs = [(int(sid), sname) for sid, sname in zip(request.POST.getlist('sid'), request.POST.getlist('sname'))]
+ streets_pairs = [(int(sid), sname) for sid, sname in
+ zip(request.POST.getlist('sid'), request.POST.getlist('sname'))]
for sid, sname in streets_pairs:
street = models.AbonStreet.objects.get(pk=sid)
street.name = sname
@@ -872,7 +822,7 @@ def street_del(request, gid, sid):
@login_required
-@mydefs.only_admins
+@permission_required('abonapp.can_view_additionaltelephones')
def tels(request, gid, uid):
abon = get_object_or_404(models.Abon, pk=uid)
telephones = abon.additional_telephones.all()
@@ -923,17 +873,17 @@ def tel_del(request, gid, uid):
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.int_ip(),
- 'is_active': abn.is_active
- } for abn in models.Abon.objects.all()]
+ 'id': abn.pk,
+ 'tarif_id': abn.active_tariff().tariff.pk if abn.active_tariff() is not None else 0,
+ 'ip': abn.ip_address.int_ip(),
+ 'is_active': abn.is_active
+ } for abn in models.Abon.objects.all()]
tarlist = [{
- 'id': trf.pk,
- 'speedIn': trf.speedIn,
- 'speedOut': trf.speedOut
- } for trf in Tariff.objects.all()]
+ 'id': trf.pk,
+ 'speedIn': trf.speedIn,
+ 'speedOut': trf.speedOut
+ } for trf in Tariff.objects.all()]
data = {
'subscribers': ablist,
diff --git a/accounts_app/forms.py b/accounts_app/forms.py
index fbedf6f..4288081 100644
--- a/accounts_app/forms.py
+++ b/accounts_app/forms.py
@@ -1,12 +1,24 @@
# -*- coding: utf-8 -*-
-from django import forms
-from .models import UserProfile
+from guardian.forms import UserObjectPermissionsForm
+from guardian.shortcuts import assign_perm, remove_perm
-class SetupPerms(forms.ModelForm):
- class Meta:
- model = UserProfile
- fields = ['user_permissions']
- widgets = {
- 'user_permissions': forms.CheckboxSelectMultiple()
- }
+class MyUserObjectPermissionsForm(UserObjectPermissionsForm):
+
+ def save_obj_perms(self):
+ """
+ Saves selected object permissions by creating new ones and removing
+ those which were not selected but already exists.
+
+ Should be called *after* form is validated.
+ """
+ perms = set(self.cleaned_data[self.get_obj_perms_field_name()])
+ model_perms = set([c[0] for c in self.get_obj_perms_field_choices()])
+ init_perms = set(self.get_obj_perms_field_initial())
+
+ to_remove = (model_perms - perms) & init_perms
+ for perm in to_remove:
+ remove_perm(perm, self.user, self.obj)
+
+ for perm in perms - init_perms:
+ assign_perm(perm, self.user, self.obj)
diff --git a/accounts_app/locale/ru/LC_MESSAGES/django.po b/accounts_app/locale/ru/LC_MESSAGES/django.po
index d3c7a5c..1de16c6 100644
--- a/accounts_app/locale/ru/LC_MESSAGES/django.po
+++ b/accounts_app/locale/ru/LC_MESSAGES/django.po
@@ -33,18 +33,6 @@ msgstr "Администраторы"
msgid "Groups"
msgstr "Группы"
-#: accounts_app/templates/accounts/group.html:16
-msgid "The current distribution of rights for groups"
-msgstr "Действующее распределение прав для группы"
-
-#: accounts_app/templates/accounts/group.html:22
-msgid "Available rights"
-msgstr "Доступные права"
-
-#: accounts_app/templates/accounts/group.html:36
-msgid "Rights for the group"
-msgstr "Права группы"
-
#: accounts_app/templates/accounts/group.html:44
#: accounts_app/templates/accounts/profile_chgroup.html:20
#: accounts_app/templates/accounts/settings/ch_info.html:66
@@ -58,22 +46,6 @@ msgstr "Сохранить"
msgid "Reset"
msgstr "Сбросить"
-#: accounts_app/templates/accounts/group_list.html:11
-msgid "Admin groups list"
-msgstr "Список групп администраторов"
-
-#: accounts_app/templates/accounts/group_list.html:20
-msgid "Group"
-msgstr "Группа"
-
-#: accounts_app/templates/accounts/group_list.html:40
-msgid "Groups does not found"
-msgstr "Нет групп"
-
-#: accounts_app/templates/accounts/group_list.html:47
-msgid "Add group"
-msgstr "Добавить группу"
-
#: accounts_app/templates/accounts/index.html:8
#: accounts_app/templates/accounts/settings/ch_info.html:37
msgid "Telephone"
@@ -186,11 +158,47 @@ msgstr "Настройка прав"
msgid "Edit"
msgstr "Редактировать"
-msgid "Set a task"
-msgstr "Дать задачу"
-
msgid "Please select an image"
msgstr "Пожалуйста выберите изображение"
msgid "Avatar successfully changed"
msgstr "Аватар успешно изменён"
+
+msgid "Access to groups"
+msgstr "Доступ к группам"
+
+msgid "The list of user groups to which the account has access"
+msgstr "Список групп абонентов, к которым учётка имеет доступ"
+
+msgid "The responsibility of the staff of the group of subscribers"
+msgstr "Ответственность работника за группы абонентов"
+
+msgid "Not set"
+msgstr "Не найдено"
+
+msgid "Change permission for that object"
+msgstr "Изменение прав доступа для выбранного объекта"
+
+msgid "Permissions has successfully updated"
+msgstr "Права успешно обновлены"
+
+msgid "Profile is superuser, permissions to change it makes no sense"
+msgstr "Учётная запись является суперпользователем, разрешения менять нет смысла"
+
+msgid "Staff account profile"
+msgstr "Учётная запись работника"
+
+msgid "Staff account profiles"
+msgstr "Учётные записи работников"
+
+msgid "Can view staff profile"
+msgstr "Может просматривать учётку сотрудника"
+
+msgid "Pick object for edit permissions"
+msgstr "Выберите объект для редактирования прав доступа"
+
+msgid "Pick the type of object"
+msgstr "Выберите тип объекта"
+
+msgid "Profile has been deleted"
+msgstr "Учётная запись удалена"
diff --git a/accounts_app/migrations/0008_auto_20170927_1838.py b/accounts_app/migrations/0008_auto_20170927_1838.py
new file mode 100644
index 0000000..fb3c6fe
--- /dev/null
+++ b/accounts_app/migrations/0008_auto_20170927_1838.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9 on 2017-09-27 18:38
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('accounts_app', '0007_auto_20170816_1109'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='userprofile',
+ options={'permissions': (('can_view_userprofile', 'Может просматривать учётку сотрудника'),), 'verbose_name': 'Учётная запись работника', 'verbose_name_plural': 'Учётные записи работников'},
+ ),
+ ]
diff --git a/accounts_app/models.py b/accounts_app/models.py
index a5f5238..94574d8 100644
--- a/accounts_app/models.py
+++ b/accounts_app/models.py
@@ -43,6 +43,9 @@ class MyUserManager(BaseUserManager):
user.save(using=self._db)
return user
+ def get_queryset(self):
+ return super(MyUserManager, self).get_queryset().filter(is_admin=True)
+
class UserProfile(AbstractBaseUser, PermissionsMixin):
username = models.CharField(max_length=127, unique=True)
@@ -91,3 +94,10 @@ class UserProfile(AbstractBaseUser, PermissionsMixin):
def __str__(self):
return self.get_full_name()
+
+ class Meta:
+ permissions = (
+ ('can_view_userprofile', _('Can view staff profile')),
+ )
+ verbose_name = _('Staff account profile')
+ verbose_name_plural = _('Staff account profiles')
diff --git a/accounts_app/templates/accounts/ext.htm b/accounts_app/templates/accounts/ext.htm
index cc08e5c..2c7e829 100644
--- a/accounts_app/templates/accounts/ext.htm
+++ b/accounts_app/templates/accounts/ext.htm
@@ -22,11 +22,24 @@
{% endif %}
@@ -46,6 +59,15 @@
{% trans 'Groups' %}
+ {% if request.user.is_superuser %}
+ {% url 'acc_app:set_abon_groups_permission' uid as set_ag_perm %}
+
+
+
+ {% trans 'Access to groups' %}
+
+
+ {% endif %}
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/accounts_app/templates/accounts/group.html b/accounts_app/templates/accounts/group.html
deleted file mode 100644
index 96e0120..0000000
--- a/accounts_app/templates/accounts/group.html
+++ /dev/null
@@ -1,50 +0,0 @@
-{% extends 'base.html' %}
-{% load i18n %}
-{% block main %}
-
-
-
- - {% trans 'Administrators' %}
- - {% trans 'Groups' %}
- - {{ group.name }}
-
-
- {% include 'message_block.html' %}
-
-
-
- {% trans 'The current distribution of rights for groups' %} {{ group.name }}
-
-
-
-
-{% endblock %}
\ No newline at end of file
diff --git a/accounts_app/templates/accounts/group_list.html b/accounts_app/templates/accounts/group_list.html
deleted file mode 100644
index fd90afd..0000000
--- a/accounts_app/templates/accounts/group_list.html
+++ /dev/null
@@ -1,58 +0,0 @@
-{% extends 'base.html' %}
-{% load i18n %}
-{% block main %}
-
-
-
- - {% trans 'Administrators' %}
- - {% trans 'Groups' %}
-
-
- {% trans 'Admin groups list' %}
-
- {% include 'message_block.html' %}
-
-
-
- {% include 'toolbar_page.html' with pag=groups %}
-
-{% endblock %}
\ No newline at end of file
diff --git a/accounts_app/templates/accounts/perms/objects_of_type.html b/accounts_app/templates/accounts/perms/objects_of_type.html
new file mode 100644
index 0000000..e5adbcf
--- /dev/null
+++ b/accounts_app/templates/accounts/perms/objects_of_type.html
@@ -0,0 +1,34 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% block main %}
+
+
+
+ - {% trans 'Administrators' %}
+ - {{ userprofile.username }}
+ - {% trans 'Permission options' %}
+ - <{{ klass }}> {{ klass_name }}
+
+
+ {% trans 'Pick object for edit permissions' %}
+
+ {% include 'message_block.html' %}
+
+
+
+
+
+ | obj |
+
+
+
+ {% for obj in objects %}|
+ {{ obj }}
+ | {% endfor %}
+
+
+
+
+ {% include 'toolbar_page.html' with pag=objects %}
+
+{% endblock %}
diff --git a/accounts_app/templates/accounts/perms/objects_types.html b/accounts_app/templates/accounts/perms/objects_types.html
new file mode 100644
index 0000000..a831c76
--- /dev/null
+++ b/accounts_app/templates/accounts/perms/objects_types.html
@@ -0,0 +1,36 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% load acc_tags %}
+{% block main %}
+
+
+
+ - {% trans 'Administrators' %}
+ - {{ userprofile.username }}
+ - {% trans 'Permission options' %}
+
+
+ {% trans 'Pick the type of object' %}
+
+ {% include 'message_block.html' %}
+
+
+
+{% endblock %}
diff --git a/accounts_app/templates/accounts/perms/perms_edit.html b/accounts_app/templates/accounts/perms/perms_edit.html
new file mode 100644
index 0000000..1e0cf96
--- /dev/null
+++ b/accounts_app/templates/accounts/perms/perms_edit.html
@@ -0,0 +1,71 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% load guardian_tags %}
+{% block main %}
+
+
+
+ - {% trans 'Administrators' %}
+ - {{ userprofile.username }}
+ - {% trans 'Permission options' %}
+ - <{{ klass }}> {{ klass_name }}
+
+ - {{ obj }}
+
+
+ {% trans 'Pick object for edit permissions' %}
+
+ {% include 'message_block.html' %}
+
+ {% if userprofile.is_superuser %}
+
+
+
+ {% trans 'Profile is superuser, permissions to change it makes no sense' %}
+
+ {% endif %}
+
+
+
+ {% trans 'Change permission for that object' %}
+
+
+
+
+{% endblock %}
diff --git a/accounts_app/templates/accounts/profile_chgroup.html b/accounts_app/templates/accounts/profile_chgroup.html
index 22156fa..c5ac443 100644
--- a/accounts_app/templates/accounts/profile_chgroup.html
+++ b/accounts_app/templates/accounts/profile_chgroup.html
@@ -2,15 +2,15 @@
{% load i18n %}
{% block content %}
-
+
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/accounts_app/templates/accounts/set_abon_groups_permission.html b/accounts_app/templates/accounts/set_abon_groups_permission.html
new file mode 100644
index 0000000..b431f98
--- /dev/null
+++ b/accounts_app/templates/accounts/set_abon_groups_permission.html
@@ -0,0 +1,25 @@
+{% extends request.is_ajax|yesno:'nullcont.htm,accounts/ext.htm' %}
+{% load i18n %}
+{% block content %}
+
+
+
+
+{% endblock %}
diff --git a/accounts_app/templates/accounts/settings/ext.htm b/accounts_app/templates/accounts/settings/ext.htm
index 92c5b3c..342ecd1 100644
--- a/accounts_app/templates/accounts/settings/ext.htm
+++ b/accounts_app/templates/accounts/settings/ext.htm
@@ -36,13 +36,6 @@
{% trans 'Change self onfo' %}
- {% if user.is_superuser %}
-
-
- {% trans 'Permission options' %}
-
-
- {% endif %}
diff --git a/accounts_app/templates/accounts/settings/permissions.html b/accounts_app/templates/accounts/settings/permissions.html
deleted file mode 100644
index 906b485..0000000
--- a/accounts_app/templates/accounts/settings/permissions.html
+++ /dev/null
@@ -1,14 +0,0 @@
-{% extends request.is_ajax|yesno:'nullcont.htm,accounts/settings/ext.htm' %}
-{% block content %}
-
-
-{% endblock %}
\ No newline at end of file
diff --git a/accounts_app/templates/accounts/settings/test.html b/accounts_app/templates/accounts/settings/test.html
new file mode 100644
index 0000000..62441f6
--- /dev/null
+++ b/accounts_app/templates/accounts/settings/test.html
@@ -0,0 +1,14 @@
+
+
+
+
diff --git a/accounts_app/templatetags/__init__.py b/accounts_app/templatetags/__init__.py
new file mode 100644
index 0000000..3474bc8
--- /dev/null
+++ b/accounts_app/templatetags/__init__.py
@@ -0,0 +1 @@
+__author__ = 'bashmak'
diff --git a/accounts_app/templatetags/acc_tags.py b/accounts_app/templatetags/acc_tags.py
new file mode 100644
index 0000000..dd29299
--- /dev/null
+++ b/accounts_app/templatetags/acc_tags.py
@@ -0,0 +1,18 @@
+from django import template
+from django.db.models import Model
+from django.apps import apps
+from six import string_types, class_types
+register = template.Library()
+
+
+
+@register.simple_tag
+def klass_name(klass):
+ if type(klass) is class_types and issubclass(klass, Model):
+ kl = klass
+ elif isinstance(klass, string_types):
+ app_label, model_name = klass.split('.', 1)
+ kl = apps.get_model(app_label, model_name)
+ else:
+ return 'Type not detected'
+ return kl._meta.verbose_name
diff --git a/accounts_app/urls.py b/accounts_app/urls.py
index adeb5ef..5224c1f 100644
--- a/accounts_app/urls.py
+++ b/accounts_app/urls.py
@@ -20,13 +20,11 @@ urlpatterns = [
url(r'^(?P \d+)$', views.profile_show, name='other_profile'),
url(r'^(?P\d+)/perms$', views.perms, name='setup_perms'),
+ url(r'^(?P\d+)/perms/(?P[a-z_]+\.[a-zA-Z_]+)$', views.perms_klasses, name='perms_klasses'),
+ url(r'^(?P\d+)/perms/(?P[a-z_]+\.[a-zA-Z_]+)/(?P\d+)$', views.perms_edit, name='perms_edit'),
url(r'^(?P\d+)/chgroup$', views.chgroup, name='profile_setup_group'),
url(r'^(?P\d+)/del$', views.delete_profile, name='delete_profile'),
- # назначить задание
- url(r'^(?P\d+)/appoint_task$', views.appoint_task, name='appoint_task'),
-
- url(r'^group/$', views.groups, name='groups_list'),
- url(r'^group/(?P\d+)$', views.group, name='group_link')
+ url(r'^(?P\d+)/user_group_access$', views.set_abon_groups_permission, name='set_abon_groups_permission')
]
\ No newline at end of file
diff --git a/accounts_app/views.py b/accounts_app/views.py
index 0227436..8b6af02 100644
--- a/accounts_app/views.py
+++ b/accounts_app/views.py
@@ -1,10 +1,9 @@
# -*- coding: utf-8 -*-
-from django.contrib.auth.decorators import login_required, permission_required
+from django.contrib.auth.decorators import login_required
from django.contrib.auth import authenticate, login, logout
from django.core.exceptions import PermissionDenied
from django.core.urlresolvers import NoReverseMatch
from django.shortcuts import render, redirect, get_object_or_404, resolve_url
-from django.contrib.auth.models import Group, Permission
from django.contrib import messages
from django.utils.translation import ugettext as _
from abonapp.models import AbonGroup
@@ -12,6 +11,8 @@ from abonapp.models import AbonGroup
from photo_app.models import Photo
from .models import UserProfile
import mydefs
+from guardian.decorators import permission_required_or_403 as permission_required
+from guardian.shortcuts import get_objects_for_user, assign_perm, remove_perm
@login_required
@@ -62,6 +63,8 @@ def profile_show(request, uid=0):
return redirect('acc_app:other_profile', uid=request.user.id)
usr = get_object_or_404(UserProfile, id=uid)
+ if request.user != usr and not request.user.has_perm('accounts_app.can_view_userprofile', usr):
+ raise PermissionDenied
if request.method == 'POST':
usr.username = request.POST.get('username')
usr.fio = request.POST.get('fio')
@@ -85,12 +88,14 @@ def chgroup(request, uid):
usr = request.user
else:
usr = get_object_or_404(UserProfile, id=uid)
+ if usr != request.user and not request.user.has_perm('accounts_app.change_userprofile', usr):
+ raise PermissionDenied
if request.method == 'POST':
ag = request.POST.getlist('ag')
usr.abon_groups.clear()
usr.abon_groups.add(*[int(d) for d in ag])
usr.save()
- abongroups = AbonGroup.objects.all()
+ abongroups = AbonGroup.objects.only('pk', 'title')
return render(request, 'accounts/profile_chgroup.html', {
'uid': uid,
'userprofile': usr,
@@ -155,7 +160,7 @@ def ch_info(request):
@login_required
-@permission_required('acc_app.add_userprofile')
+@permission_required('accounts_app.add_userprofile')
def create_profile(request):
if request.method == 'POST':
username = request.POST.get('username')
@@ -194,18 +199,20 @@ def create_profile(request):
@login_required
@mydefs.only_admins
def delete_profile(request, uid):
+ prf = get_object_or_404(UserProfile, id=uid)
if uid != request.user.id:
- if not request.user.has_perm('acc_app.delete_userprofile'):
+ if not request.user.has_perm('acc_app.delete_userprofile', prf):
raise PermissionDenied
- prf = get_object_or_404(UserProfile, id=uid)
prf.delete()
+ messages.success(request, _('Profile has been deleted'))
return redirect('acc_app:accounts_list')
@login_required
@mydefs.only_admins
def acc_list(request):
- users = UserProfile.objects.filter(is_admin=True)
+ users = UserProfile.objects.filter(is_admin=True).exclude(pk=request.user.pk)
+ users = get_objects_for_user(request.user, 'accounts_app.can_view_userprofile', users)
users = mydefs.pag_mn(request, users)
return render(request, 'accounts/acc_list.html', {
'users': users
@@ -213,54 +220,88 @@ def acc_list(request):
@login_required
-@mydefs.only_admins
def perms(request, uid):
- profile = get_object_or_404(UserProfile, id=uid)
- own_permissions = UserProfile.get_all_permissions(profile)
- return render(request, 'accounts/settings/permissions.html', {
- 'uid': uid,
- 'own_permissions': own_permissions
+ if not request.user.is_superuser:
+ raise PermissionDenied
+ userprofile = get_object_or_404(UserProfile, id=uid)
+ klasses = (
+ 'abonapp.AbonGroup', 'abonapp.Abon', 'accounts_app.UserProfile',
+ 'abonapp.AbonTariff', 'abonapp.AbonStreet', 'devapp.Device',
+ 'abonapp.PassportInfo', 'abonapp.AdditionalTelephone'
+ )
+ return render(request, 'accounts/perms/objects_types.html', {
+ 'userprofile': userprofile,
+ 'klasses': klasses
})
@login_required
-@mydefs.only_admins
-def groups(request):
- grps = Group.objects.all()
- grps = mydefs.pag_mn(request, grps)
- return render(request, 'accounts/group_list.html', {
- 'groups': grps
+def perms_klasses(request, uid, klass_name):
+ if not request.user.is_superuser:
+ raise PermissionDenied
+ from django.apps import apps
+ userprofile = get_object_or_404(UserProfile, pk=uid)
+ app_label, model_name = klass_name.split('.', 1)
+ klass = apps.get_model(app_label, model_name)
+
+ objects = klass.objects.all()
+ objects = mydefs.pag_mn(request, objects)
+
+ return render(request, 'accounts/perms/objects_of_type.html', {
+ 'userprofile': userprofile,
+ 'klass': klass_name,
+ 'klass_name': klass._meta.verbose_name,
+ 'objects': objects
})
-
@login_required
-@mydefs.only_admins
-def group(request, uid):
- uid = mydefs.safe_int(uid)
- grp = get_object_or_404(Group, id=uid)
-
- if request.method == 'POST':
- group_rights = filter(lambda x: x[0] == 'group_rights', request.POST.lists())[0][1]
- grp.permissions.clear()
- for grr in group_rights:
- rid = mydefs.safe_int(grr)
- grp.permissions.add(rid)
- grp.save()
- return redirect('acc_app:profile_group_link', id=uid)
-
- grp_rights = grp.permissions.all()
- all_rights = Permission.objects.exclude(group=grp)
-
- return render(request, 'accounts/group.html', {
- 'group': grp,
- 'all_rights': all_rights,
- 'grp_rights': grp_rights
+def perms_edit(request, uid, klass_name, obj_id):
+ if not request.user.is_superuser:
+ raise PermissionDenied
+ from django.apps import apps
+ from .forms import MyUserObjectPermissionsForm
+ userprofile = get_object_or_404(UserProfile, pk=uid)
+ app_label, model_name = klass_name.split('.', 1)
+ klass = apps.get_model(app_label, model_name)
+ obj = get_object_or_404(klass, pk=obj_id)
+
+ frm = MyUserObjectPermissionsForm(userprofile, obj, request.POST or None)
+ if request.method == 'POST' and frm.is_valid():
+ frm.save_obj_perms()
+ messages.success(request, _('Permissions has successfully updated'))
+
+ return render(request, 'accounts/perms/perms_edit.html', {
+ 'userprofile': userprofile,
+ 'obj': obj,
+ 'form': frm,
+ 'klass': klass_name,
+ 'klass_name': klass._meta.verbose_name
})
@login_required
-@mydefs.only_admins
-def appoint_task(req, uid):
- uid = mydefs.safe_int(uid)
- url = resolve_url('taskapp:add')
- return redirect("%s?rp=%d" % (url, uid))
+def set_abon_groups_permission(request, uid):
+ # Only superuser can change object permissions
+ if not request.user.is_superuser:
+ raise PermissionDenied
+ userprofile = get_object_or_404(UserProfile, pk=uid)
+
+ picked_groups = get_objects_for_user(userprofile, 'abonapp.can_view_abongroup', accept_global_perms=False)
+ picked_groups = picked_groups.values_list('pk', flat=True)
+
+ if request.method == 'POST':
+ checked_groups = [int(ag) for ag in request.POST.getlist('ag', default=0)]
+ for abon_group in AbonGroup.objects.all():
+ if abon_group.pk in checked_groups and abon_group.pk not in picked_groups:
+ assign_perm('abonapp.can_view_abongroup', userprofile, obj=abon_group)
+ elif abon_group.pk not in checked_groups and abon_group.pk in picked_groups:
+ remove_perm('abonapp.can_view_abongroup', userprofile, obj=abon_group)
+ return redirect('acc_app:set_abon_groups_permission', uid)
+ abongroups = AbonGroup.objects.only('pk', 'title')
+
+ return render(request, 'accounts/set_abon_groups_permission.html', {
+ 'uid': uid,
+ 'userprofile': userprofile,
+ 'abongroups': abongroups,
+ 'picked_groups_ids': picked_groups
+ })
diff --git a/clientsideapp/templates/clientsideapp/ext.html b/clientsideapp/templates/clientsideapp/ext.html
index b19ef15..ba81d1a 100644
--- a/clientsideapp/templates/clientsideapp/ext.html
+++ b/clientsideapp/templates/clientsideapp/ext.html
@@ -93,7 +93,10 @@
diff --git a/devapp/locale/ru/LC_MESSAGES/django.po b/devapp/locale/ru/LC_MESSAGES/django.po
index 7a6f8e3..77cfd06 100644
--- a/devapp/locale/ru/LC_MESSAGES/django.po
+++ b/devapp/locale/ru/LC_MESSAGES/django.po
@@ -300,3 +300,12 @@ msgstr "Посмотреть устройство"
msgid "Eltex switch"
msgstr "Элтекс свич"
+
+msgid "Can view device"
+msgstr "Может видеть устройство"
+
+msgid "Device"
+msgstr "Устройство"
+
+msgid "Can toggle ports"
+msgstr "Может переключать порты"
diff --git a/devapp/migrations/0003_auto_20170927_1838.py b/devapp/migrations/0003_auto_20170927_1838.py
new file mode 100644
index 0000000..490780c
--- /dev/null
+++ b/devapp/migrations/0003_auto_20170927_1838.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9 on 2017-09-27 18:38
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('devapp', '0002_device_user_group'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='device',
+ options={'permissions': (('can_view_device', 'Can view device'),), 'verbose_name': 'Device', 'verbose_name_plural': 'Devices'},
+ ),
+ migrations.AlterModelOptions(
+ name='port',
+ options={'permissions': (('can_toggle_ports', 'Can toggle ports'),), 'verbose_name': 'Port', 'verbose_name_plural': 'Ports'},
+ ),
+ ]
diff --git a/devapp/models.py b/devapp/models.py
index aaf0842..c21f07f 100644
--- a/devapp/models.py
+++ b/devapp/models.py
@@ -8,6 +8,7 @@ from . import dev_types
from mapapp.models import Dot
from subprocess import call
from django.conf import settings
+from django.utils.translation import ugettext_lazy as _
DEVICE_TYPES = (
@@ -34,6 +35,11 @@ class Device(models.Model):
class Meta:
db_table = 'dev'
+ permissions = (
+ ('can_view_device', _('Can view device')),
+ )
+ verbose_name = _('Device')
+ verbose_name_plural = _('Devices')
def get_abons(self):
pass
@@ -69,6 +75,11 @@ class Port(models.Model):
class Meta:
db_table = 'dev_port'
unique_together = (('device', 'num'))
+ permissions = (
+ ('can_toggle_ports', _('Can toggle ports')),
+ )
+ verbose_name = _('Port')
+ verbose_name_plural = _('Ports')
def dev_post_save_signal(sender, instance, **kwargs):
diff --git a/devapp/templates/devapp/custom_dev_page/onu.html b/devapp/templates/devapp/custom_dev_page/onu.html
index 0a70d20..7ebb774 100644
--- a/devapp/templates/devapp/custom_dev_page/onu.html
+++ b/devapp/templates/devapp/custom_dev_page/onu.html
@@ -14,25 +14,28 @@
- - {% trans 'Ip address' %}: {{ dev.ip_address }}
- - {% trans 'Mac' %}: {{ dev.mac_addr }}
- - {% trans 'Description' %} {{ dev.comment }}
+ - {% trans 'Ip address' %}: {{ dev.ip_address }}
+ - {% trans 'Mac' %}: {{ dev.mac_addr }}
+ - {% trans 'Description' %} {{ dev.comment }}
{% for da in dev_accs %}
- - {% trans 'Attached user' %}:
- {% if da.group %}
- {{ da.get_full_name }}
- {% else %}
- {{ da.get_full_name }}
- {% endif %}
-
+ - {% trans 'Attached user' %}:
+ {% if da.group %}
+ {{ da.get_full_name }}
+ {% else %}
+ {{ da.get_full_name }}
+ {% endif %}
+
{% endfor %}
- {% if dev.parent_dev %}
- -
- {% with pdev=dev.parent_dev pdgrp=dev.parent_dev.user_group %}
- {% trans 'Parent device' %}: {{ pdev.ip_address }} {{ pdev.comment }}
- {% endwith %}
-
- {% endif %}
+ {% if dev.parent_dev %}
+ -
+ {% with pdev=dev.parent_dev pdgrp=dev.parent_dev.user_group %}
+ {% trans 'Parent device' %}:{{ pdev.ip_address }} {{ pdev.comment }}
+ {% endwith %}
+
+ {% endif %}
diff --git a/devapp/templates/devapp/devices_null_group.html b/devapp/templates/devapp/devices_null_group.html
index 569825c..816bcbe 100644
--- a/devapp/templates/devapp/devices_null_group.html
+++ b/devapp/templates/devapp/devices_null_group.html
@@ -38,19 +38,20 @@
+ {% with can_del_dev=perms.devapp.delete_device can_change_dev=perms.devapp.change_device %}
{% for dev in devices %}
- | {{ dev.ip_address }} |
+ {{ dev.ip_address }} |
{{ dev.comment }} |
{{ dev.get_devtype_display }} |
- {% if perms.devapp.delete_device %}
-
+ {% if can_del_dev %}
+
{% endif %}
- {% if perms.devapp.change_device %}
-
+ {% if can_change_dev %}
+
{% endif %}
@@ -61,6 +62,7 @@
| {% trans 'Devices does not found' %}. {% trans 'Create' %} |
{% endfor %}
+ {% endwith %}
diff --git a/devapp/views.py b/devapp/views.py
index f332ef7..1b45b0a 100644
--- a/devapp/views.py
+++ b/devapp/views.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-from django.contrib.auth.decorators import login_required, permission_required
+from django.contrib.auth.decorators import login_required
from django.contrib.gis.shortcuts import render_to_text
from django.core.exceptions import PermissionDenied
from django.db.models import Q
@@ -15,13 +15,18 @@ from mydefs import pag_mn, res_success, res_error, only_admins, ping, order_help
from .forms import DeviceForm, PortForm
from abonapp.models import AbonGroup, Abon
from django.conf import settings
+from guardian.decorators import permission_required_or_403 as permission_required
+from guardian.shortcuts import get_objects_for_user
@login_required
@only_admins
def devices(request, grp):
group = get_object_or_404(AbonGroup, pk=grp)
- devs = Device.objects.filter(user_group=grp)
+ if not request.user.has_perm('abonapp.can_view_abongroup', group):
+ raise PermissionDenied
+ devs = Device.objects.filter(user_group=grp).only('comment', 'mac_addr', 'devtype', 'user_group', 'pk',
+ 'ip_address')
# фильтр
dr, field = order_helper(request)
@@ -41,7 +46,7 @@ def devices(request, grp):
@login_required
@only_admins
def devices_null_group(request):
- devs = Device.objects.filter(user_group=None)
+ devs = Device.objects.filter(user_group=None).only('comment', 'devtype', 'user_group', 'pk', 'ip_address')
# фильтр
dr, field = order_helper(request)
if field:
@@ -71,10 +76,12 @@ def devdel(request, did):
@login_required
-@only_admins
+@permission_required('devapp.can_view_device')
def dev(request, grp, devid=0):
- devinst = get_object_or_404(Device, id=devid) if devid != 0 else None
user_group = get_object_or_404(AbonGroup, pk=grp)
+ if not request.user.has_perm('abonapp.can_view_abongroup', user_group):
+ raise PermissionDenied
+ devinst = get_object_or_404(Device, id=devid) if devid != 0 else None
already_dev = None
if request.method == 'POST':
@@ -294,7 +301,7 @@ def add_single_port(request, grp, did):
@login_required
-@only_admins
+@permission_required('devapp.can_view_device')
def devview(request, did):
ports = None
uptime = 0
@@ -318,7 +325,7 @@ def devview(request, did):
except DeviceDBException as e:
messages.error(request, e)
- return render(request, 'devapp/custom_dev_page/'+template_name, {
+ return render(request, 'devapp/custom_dev_page/' + template_name, {
'dev': dev,
'ports': ports,
'uptime': uptime,
@@ -327,7 +334,7 @@ def devview(request, did):
@login_required
-@only_admins
+@permission_required('devapp.can_toggle_ports')
def toggle_port(request, did, portid, status=0):
portid = int(portid)
status = int(status)
@@ -338,9 +345,9 @@ def toggle_port(request, did, portid, status=0):
manager = dev.get_manager_klass()(dev.ip_address, dev.man_passw)
ports = manager.get_ports()
if status:
- ports[portid-1].enable()
+ ports[portid - 1].enable()
else:
- ports[portid-1].disable()
+ ports[portid - 1].disable()
else:
messages.warning(request, _('Not Set snmp device password'))
else:
@@ -353,7 +360,8 @@ def toggle_port(request, did, portid, status=0):
@login_required
@only_admins
def group_list(request):
- groups = AbonGroup.objects.all()
+ groups = AbonGroup.objects.all().order_by('title')
+ groups = get_objects_for_user(request.user, 'abonapp.can_view_abongroup', klass=groups, accept_global_perms=False)
return render(request, 'devapp/group_list.html', {
'groups': groups
})
@@ -365,6 +373,8 @@ def search_dev(request):
if word is None:
results = [{'id': 0, 'text': ''}]
else:
- results = Device.objects.filter(Q(comment__icontains=word) | Q(ip_address=word))[:16]
+ results = Device.objects.filter(
+ Q(comment__icontains=word) | Q(ip_address=word)
+ ).only('pk', 'ip_address', 'comment')[:16]
results = [{'id': dev.pk, 'text': "%s: %s" % (dev.ip_address, dev.comment)} for dev in results]
return HttpResponse(dumps(results, ensure_ascii=False))
diff --git a/djing/settings_example.py b/djing/settings_example.py
index db346b8..b43ae49 100644
--- a/djing/settings_example.py
+++ b/djing/settings_example.py
@@ -16,6 +16,11 @@ DEBUG = True
ALLOWED_HOSTS = ['*']
+# required for django-guardian
+AUTHENTICATION_BACKENDS = (
+ 'django.contrib.auth.backends.ModelBackend', # default
+ 'guardian.backends.ObjectPermissionBackend'
+)
# Application definition
@@ -38,7 +43,8 @@ INSTALLED_APPS = [
'clientsideapp',
'chatbot',
'django_messages',
- 'dialing_app'
+ 'dialing_app',
+ 'guardian'
]
MIDDLEWARE_CLASSES = [
diff --git a/mapapp/views.py b/mapapp/views.py
index afb60ad..acdf717 100644
--- a/mapapp/views.py
+++ b/mapapp/views.py
@@ -1,5 +1,5 @@
from django.contrib import messages
-from django.contrib.auth.decorators import login_required, permission_required
+from django.contrib.auth.decorators import login_required
from django.contrib.gis.shortcuts import render_to_text
from django.core.exceptions import PermissionDenied
from django.http import HttpResponse
@@ -9,6 +9,7 @@ from .models import Dot
from .forms import DotForm
from mydefs import pag_mn
from devapp.models import Device
+from guardian.decorators import permission_required_or_403 as permission_required
@login_required
diff --git a/requirements.txt b/requirements.txt
index 7d485d0..447c20d 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -14,3 +14,5 @@ mysqlclient
easysnmp
rq
pid
+django-guardian
+jsonfield
diff --git a/tariff_app/views.py b/tariff_app/views.py
index 9b8cd0c..4c766f9 100644
--- a/tariff_app/views.py
+++ b/tariff_app/views.py
@@ -1,10 +1,11 @@
# -*- coding: utf-8 -*-
-from django.contrib.auth.decorators import login_required, permission_required
+from django.contrib.auth.decorators import login_required
from django.contrib.gis.shortcuts import render_to_text
from django.utils.translation import ugettext as _
from django.shortcuts import render, get_object_or_404, redirect
from django.contrib import messages
from django.core.exceptions import PermissionDenied
+from guardian.decorators import permission_required_or_403 as permission_required
from .models import Tariff
import mydefs
diff --git a/taskapp/views.py b/taskapp/views.py
index d8ec471..7ba4159 100644
--- a/taskapp/views.py
+++ b/taskapp/views.py
@@ -1,11 +1,12 @@
# coding=utf-8
-from django.contrib.auth.decorators import login_required, permission_required
+from django.contrib.auth.decorators import login_required
from django.core.exceptions import PermissionDenied
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib import messages
from abonapp.models import Abon
from django.utils.translation import ugettext as _
from datetime import date
+from guardian.decorators import permission_required_or_403 as permission_required
from .handle import TaskException
from .models import Task
diff --git a/templates/all_base.html b/templates/all_base.html
index 956692e..9c89b2d 100644
--- a/templates/all_base.html
+++ b/templates/all_base.html
@@ -47,7 +47,7 @@
Другое
|