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 %} - - - - {% include 'message_block.html' %} - -
-
-

{% trans 'Finish service' %}

-
-
- -
{% csrf_token %} - - -

- {% blocktrans %}Early completion of the current service will cause that user will be denied access to services Resources (Net closes) -To continue to use the resources necessary to connect the required service{% endblocktrans %} -

- -

{% blocktrans %}Details:
-You complete tariff{% endblocktrans %} - - {% if perms.tariff_app.change_tariff %} - {{ abtar.tariff.title }}.
- {% else %} - {{ abtar.tariff.title }}.
- {% endif %} - - {% now "d F Y, H: i: s" as today %} - {% blocktrans with time_start=abtar.time_start|date:'d F Y, H: i: s' amount=abtar.tariff.amount %}The service has been connected: {{ time_start }}
-Today: {{ today }}
-Time of use: {{ time_use }}
-The total cost of the service: {{ amount }}
-Total cost: {{ tcost }}
-Cashback: {{ cashback }}{% endblocktrans %} -

- -
- -
- -
- -
-
- -{% 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 %}
@@ -108,9 +109,6 @@ - {% if perms.taskapp.add_task %} {% trans 'Add new task' %} @@ -119,7 +117,7 @@
- {% if ip %} + {% if ip and perms.abonapp.can_ping %}
@@ -134,6 +132,7 @@
+ {% if perms.abonapp.change_abon %}
+ {% 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 @@ 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 @@ 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 %}
@@ -86,6 +87,7 @@
+ {% 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 %}
{% if userprofile == request.user %} - - {% trans 'Edit' %} + + + {% trans 'Edit' %} + + {% endif %} + {% if request.user.is_superuser %} + {% if userprofile.is_superuser %} + + + {% trans 'Permission options' %} + + {% else %} + + + {% trans 'Permission options' %} + + {% endif %} {% endif %} - - {% trans 'Set a task' %}
@@ -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 %}
@@ -55,4 +77,4 @@
-{% 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 %} - - - - {% include 'message_block.html' %} - -
-
-

{% trans 'The current distribution of rights for groups' %} {{ group.name }}

-
-
-
{% csrf_token %} - -
- - -
-
    -
  • -
  • -
-
- - -
-
- - - -
-
- -{% 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 'Admin groups list' %}

- - {% include 'message_block.html' %} - -
-
- {% 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' %} {{ 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' %}
- - - - - - - - - {% for grp in groups %} - - - - - - {% empty %} - - - - {% endfor %} - - - - - - -
#{% trans 'Group' %}
{{ grp.id }}{{ grp.name }} - - - - - - -
{% trans 'Groups does not found' %}
- - - -
- - - {% 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 'Pick object for edit permissions' %}

+ + {% include 'message_block.html' %} + +
+ + + + + + + + {% for obj in objects %}{% endfor %} + +
obj
+ {{ obj }} +
+
+ + {% 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 'Pick the type of object' %}

+ + {% include 'message_block.html' %} + +
+ + + + + + + + {% for klass in klasses %} + + + + {% endfor %} + +
{% trans 'Group' %}
+ <{{ klass }}> {% klass_name klass %} +
+
+ +{% 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 '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' %}

+
+
+ +
{% csrf_token %} + + {% get_obj_perms userprofile for obj as 'obj_perms' %} + + {% for field in form %} +
+ + +
+ +
+
+ {% endfor %} + +
+ + +
+
+
+
+ +{% 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 %} - {% trans 'The responsibility of the administrator of the group of subscribers' %} + {% trans 'The responsibility of the staff of the group of subscribers' %}
{% csrf_token %} {% for ag in abongroups %}
@@ -21,4 +21,4 @@
-{% 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 %} + + {% trans 'The list of user groups to which the account has access' %} +
{% csrf_token %} + {% for ag in abongroups %} +
+ +
+ {% endfor %} +
+ + +
+
+ +{% 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 %} - -
    {% csrf_token %} - {% for perm, pname in form.user_permissions.field.choices %} -
    - -
    - {% endfor %} -
    -{% 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 @@
    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 @@