From 535389b9c459e4a8f0819686970eef843f4dc902 Mon Sep 17 00:00:00 2001 From: Dmitry Novikov Date: Sun, 26 Aug 2018 20:07:05 +0300 Subject: [PATCH] Add logging to admin actions --- abonapp/locale/ru/LC_MESSAGES/django.po | 129 ++++++++-------- abonapp/views.py | 11 ++ accounts_app/admin.py | 3 +- accounts_app/locale/ru/LC_MESSAGES/django.po | 142 +++++++++++++----- .../migrations/0003_new_user_profile_log.py | 45 ++++++ accounts_app/models.py | 56 ++++++- .../templates/accounts/action_log.html | 29 ++++ accounts_app/templates/accounts/ext.htm | 11 ++ accounts_app/urls.py | 4 +- accounts_app/views.py | 32 +++- devapp/forms.py | 3 +- devapp/views.py | 12 +- msg_app/templates/msg_app/chat.html | 2 +- nas_app/views.py | 10 ++ tariff_app/views.py | 15 +- 15 files changed, 388 insertions(+), 116 deletions(-) create mode 100644 accounts_app/migrations/0003_new_user_profile_log.py create mode 100644 accounts_app/templates/accounts/action_log.html diff --git a/abonapp/locale/ru/LC_MESSAGES/django.po b/abonapp/locale/ru/LC_MESSAGES/django.po index 0ab4203..38cb70b 100644 --- a/abonapp/locale/ru/LC_MESSAGES/django.po +++ b/abonapp/locale/ru/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-08-25 14:55+0300\n" +"POT-Creation-Date: 2018-08-26 19:21+0300\n" "Last-Translator: Dmitry Novikov nerosketch@gmail.com\n" "Language: ru\n" "MIME-Version: 1.0\n" @@ -211,11 +211,12 @@ msgid "Buy service default log" msgstr "Покупка тарифного плана через админку" #: models.py:228 +#, python-format msgid "Account \"%(username)s\" not have any active leases" -msgstr "Учётная запись %(username)s не имеет ни одной активной сессии" +msgstr "Учётная запись \"%(username)s\" не имеет ни одной активной сессии" -#: models.py:238 models.py:255 models.py:272 views.py:676 views.py:1124 -#: views.py:1167 +#: models.py:238 models.py:255 models.py:272 views.py:684 views.py:1132 +#: views.py:1175 msgid "NAS required" msgstr "Необходимо выбрать NAS" @@ -352,7 +353,9 @@ msgstr "Статус оплаты" #: templates/abonapp/addInvoice.html:57 msgid "This credit will be visible in user page, be careful with your text." -msgstr "Этот долг будет виден на странице абонента, будьте осторожны с высказываниями." +msgstr "" +"Этот долг будет виден на странице абонента, будьте осторожны с " +"высказываниями." #: templates/abonapp/buy_tariff.html:10 templates/abonapp/buy_tariff.html:15 #: templates/abonapp/buy_tariff.html:31 templates/abonapp/service.html:74 @@ -908,134 +911,134 @@ msgstr "Это не правильный IPv6 адрес" msgid "Address" msgstr "Адрес" -#: views.py:132 +#: views.py:136 msgid "create abon success msg" msgstr "Абонент успешно создан" -#: views.py:143 views.py:317 views.py:436 views.py:532 views.py:857 -#: views.py:941 views.py:1014 views.py:1110 +#: views.py:147 views.py:325 views.py:444 views.py:540 views.py:865 +#: views.py:949 views.py:1022 views.py:1118 msgid "fix form errors" msgstr "Некоторые поля заполнены не правильно, проверте ещё раз" -#: views.py:167 +#: views.py:175 msgid "delete abon success msg" msgstr "Абонент успешно удалён" -#: views.py:172 +#: views.py:180 #, python-format msgid "NAS says: '%s'" msgstr "NAS сказал: '%s'" -#: views.py:193 +#: views.py:201 msgid "fill account through admin side" msgstr "Пополнение счёта через админку" -#: views.py:196 +#: views.py:204 #, python-format msgid "Account filled successfully on %.2f" msgstr "Счёт пополнен на %.2f" -#: views.py:199 +#: views.py:207 msgid "I not know the account id" msgstr "Счёт успешно пополнен на %.2f" -#: views.py:260 +#: views.py:268 msgid "User group id is not matches with group in url" msgstr "Группа абонента не совпадает с группой указанной в url" -#: views.py:313 +#: views.py:321 msgid "edit abon success msg" msgstr "Абонент успешно изменён" -#: views.py:324 +#: views.py:332 msgid "User device was not found" msgstr "Пользовательское устройство не найдено" -#: views.py:337 +#: views.py:345 msgid "User has not have password, and cannot login" msgstr "Для абонента не задан пароль, он не сможет войти в учётку" -#: views.py:384 +#: views.py:392 msgid "Receipt has been created" msgstr "Квитанция на оплату была создана" -#: views.py:411 +#: views.py:419 #, python-format msgid "Service '%(service_name)s' has connected via admin" msgstr "Услуга '%(service_name)s' подключена администратором" -#: views.py:421 +#: views.py:429 msgid "Tariff has been picked" msgstr "Тариф успешно выбран" -#: views.py:431 +#: views.py:439 msgid "Tariff your picked does not exist" msgstr "Тариф, который вы выбрали, не существует" -#: views.py:453 +#: views.py:461 msgid "User has been detached from service" msgstr "Абонент отвязан от услуги" -#: views.py:523 +#: views.py:531 msgid "Passport information has been saved" msgstr "Информация о паспорте сохранена" -#: views.py:554 +#: views.py:562 msgid "Successfully saved" msgstr "Успешно сохранено" -#: views.py:575 +#: views.py:583 msgid "Device has successfully attached" msgstr "Устройство успешно прикреплено" -#: views.py:580 +#: views.py:588 msgid "Device your selected already does not exist" msgstr "Устройство, выбранное вами, уже не существует" -#: views.py:582 views.py:604 views.py:642 +#: views.py:590 views.py:612 views.py:650 msgid "Abon does not exist" msgstr "Абонент не найден" -#: views.py:602 +#: views.py:610 msgid "Device has successfully unattached" msgstr "Устройство успешно откреплено" -#: views.py:645 +#: views.py:653 msgid "Group what you want doesn't exist" msgstr "Указанная вами группа не найдена" -#: views.py:667 +#: views.py:675 msgid "no ping" msgstr "не пингуется" -#: views.py:671 +#: views.py:679 msgid "Ip not passed" msgstr "Ip адрес не передан" -#: views.py:683 views.py:699 +#: views.py:691 views.py:707 msgid "ping ok" msgstr "пингуется" -#: views.py:690 +#: views.py:698 #, python-format msgid "IP Conflict! %(all)d/%(return)d results" msgstr "IP Конфликт! ping %(all)d из %(return)d" -#: views.py:693 +#: views.py:701 #, python-format msgid "ok ping, %(all)d/%(return)d loses" msgstr "пингуется, %(all)d/%(return)d" -#: views.py:697 +#: views.py:705 #, python-format msgid "no ping, %(all)d/%(return)d loses" msgstr "не пингуется, %(all)d/%(return)d" -#: views.py:803 +#: views.py:811 msgid "Method is not POST" msgstr "Метод не POST" -#: views.py:820 +#: views.py:828 #, python-format msgid "" "%(user_name)s already pinned to this port on this " @@ -1044,108 +1047,108 @@ msgstr "" "%(user_name)s уже привязан к этому порту на этом " "устройстве" -#: views.py:828 +#: views.py:836 msgid "Multiple users on the same device port" msgstr "Несколько абонентов на одном и том же порту устройства" -#: views.py:837 +#: views.py:845 msgid "User port has been saved" msgstr "Порт абонента успешно выбран" -#: views.py:839 +#: views.py:847 msgid "Selected port does not exist" msgstr "Выбранный порт не существует" -#: views.py:841 +#: views.py:849 msgid "User does not exist" msgstr "Абонент не найден" -#: views.py:854 +#: views.py:862 msgid "Street successfully saved" msgstr "Улица успешно сохранена" -#: views.py:877 +#: views.py:885 msgid "Streets has been saved" msgstr "Улицы сохранены" -#: views.py:885 +#: views.py:893 msgid "One of these streets has not been found" msgstr "Одна из этих улиц не была найдена" -#: views.py:897 +#: views.py:905 msgid "The street successfully deleted" msgstr "Улица успешно удалена" -#: views.py:899 +#: views.py:907 msgid "The street has not been found" msgstr "Улица не найдена" -#: views.py:938 +#: views.py:946 msgid "New telephone has been saved" msgstr "Новый телефон сохранен" -#: views.py:959 +#: views.py:967 msgid "Additional telephone successfully deleted" msgstr "Номер телефона успешно удалён" -#: views.py:961 +#: views.py:969 msgid "Telephone not found" msgstr "Телефон не найден" -#: views.py:1011 +#: views.py:1019 #, python-format msgid "Unexpected format %(export_format)s" msgstr "Нежиданный формат %(export_format)s" -#: views.py:1061 +#: views.py:1069 msgid "Periodic pays has been designated" msgstr "Периодический платёж назначен" -#: views.py:1063 +#: views.py:1071 msgid "Something wrong in form" msgstr "Что-то не так в форме" -#: views.py:1083 +#: views.py:1091 msgid "Periodic pay successfully deleted" msgstr "Периодический платёж успешно удалён" -#: views.py:1115 +#: views.py:1123 msgid "User flags has changed successfully" msgstr "Флаги абонента изменены успешно" -#: views.py:1133 +#: views.py:1141 msgid "Ip lease has been freed" msgstr "Аренда ip освобождена" -#: views.py:1136 +#: views.py:1144 msgid "You cannot disable last session" msgstr "Вы не можете отключить последний ip" -#: views.py:1141 +#: views.py:1149 msgid "Ip lease has been started" msgstr "Аренда ip включена" -#: views.py:1143 +#: views.py:1151 msgid "Unexpected action" msgstr "Непредвиденное действие" -#: views.py:1174 +#: views.py:1182 msgid "Ip lease has been created" msgstr "Аренда ip создана" -#: views.py:1179 +#: views.py:1187 msgid "Check form errors" msgstr "Некоторые поля заполнены не правильно, проверте ещё раз" -#: views.py:1205 +#: views.py:1213 msgid "Network access server for users in this group, has been updated" msgstr "Сервер доступа в интернет привязан к пользователям в этой группе" -#: views.py:1208 +#: views.py:1216 msgid "Users not found" msgstr "Пользователи не найдены" -#: views.py:1210 +#: views.py:1218 msgid "You must select gateway" msgstr "Вы должны выбрать шлюз" diff --git a/abonapp/views.py b/abonapp/views.py index dc5d95f..bb1d198 100644 --- a/abonapp/views.py +++ b/abonapp/views.py @@ -129,6 +129,10 @@ class AbonCreateView(CreateView): assign_perm("abonapp.can_buy_tariff", me, abon) assign_perm("abonapp.can_view_passport", me, abon) assign_perm('abonapp.can_add_ballance', me, abon) + me.log(self.request.META, 'cusr', '%s, "%s", %s' % ( + abon.username, abon.fio, + abon.group.title if abon.group else '' + )) messages.success(self.request, _('create abon success msg')) self.abon = abon return super(AbonCreateView, self).form_valid(form) @@ -164,6 +168,13 @@ class DelAbonDeleteView(DeleteView): abon = self.get_object() gid = abon.group.id abon.delete() + request.user.log(request.META, 'dusr', ('%(uname)s, "%(fio)s", %(group)s %(street)s %(house)s' % { + 'uname': abon.username, + 'fio': abon.fio or '-', + 'group': abon.group.title if abon.group else '', + 'street': abon.street.name if abon.street else '', + 'house': abon.house or '' + }).strip()) messages.success(request, _('delete abon success msg')) return redirect('abonapp:people_list', gid=gid) except NasNetworkError as e: diff --git a/accounts_app/admin.py b/accounts_app/admin.py index f5bccb6..657ee2c 100644 --- a/accounts_app/admin.py +++ b/accounts_app/admin.py @@ -1,5 +1,6 @@ from django.contrib import admin -from .models import UserProfile +from .models import UserProfile, UserProfileLog admin.site.register(UserProfile) +admin.site.register(UserProfileLog) diff --git a/accounts_app/locale/ru/LC_MESSAGES/django.po b/accounts_app/locale/ru/LC_MESSAGES/django.po index 078323b..56ece20 100644 --- a/accounts_app/locale/ru/LC_MESSAGES/django.po +++ b/accounts_app/locale/ru/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-08-09 14:54+0300\n" +"POT-Creation-Date: 2018-08-26 19:09+0300\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: Dmitry Novikov nerosketch@gmail.com\n" "Language: ru\n" @@ -18,49 +18,105 @@ msgstr "" "%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n" "%100>=11 && n%100<=14)? 2 : 3);\n" -#: models.py:19 +#: models.py:21 msgid "Users must have an telephone number" msgstr "У пользователей должен быть номер телефона" -#: models.py:47 templates/accounts/acc_list.html:21 +#: models.py:49 templates/accounts/acc_list.html:21 msgid "profile username" msgstr "Логин" -#: models.py:52 +#: models.py:54 msgid "fio" msgstr "ФИО" -#: models.py:53 +#: models.py:55 msgid "birth day" msgstr "дата рождения" -#: models.py:54 +#: models.py:56 msgid "Is active" msgstr "Активен" -#: models.py:58 templates/accounts/acc_list.html:23 +#: models.py:60 templates/accounts/acc_list.html:23 #: templates/accounts/create_acc.html:62 templates/accounts/index.html:9 #: templates/accounts/settings/ch_info.html:38 msgid "Telephone" msgstr "Телефон" +#: models.py:94 +msgid "Author" +msgstr "Автор" + +#: models.py:95 templates/accounts/action_log.html:12 +msgid "Meta information" +msgstr "Мета информация" + #: models.py:97 +msgid "Create user" +msgstr "Создание абонента" + +#: models.py:98 +msgid "Delete user" +msgstr "Удаление абонента" + +#: models.py:99 +msgid "Create device" +msgstr "Создание устройства" + +#: models.py:100 +msgid "Delete device" +msgstr "Удаление устройства" + +#: models.py:101 +msgid "Create NAS" +msgstr "Создание NAS" + +#: models.py:102 +msgid "Delete NAS" +msgstr "Удаление NAS" + +#: models.py:103 +msgid "Create service" +msgstr "Создание тарифа" + +#: models.py:104 +msgid "Delete service" +msgstr "Удаление тарифа" + +#: models.py:106 +msgid "Action type" +msgstr "Тип действия" + +#: models.py:107 +msgid "Additional info" +msgstr "Дополнительная информация" + +#: models.py:108 +msgid "Action date" +msgstr "Дата действия" + +#: models.py:115 +msgid "User profile log" +msgstr "Лог действий учётной записи" + +#: models.py:116 +msgid "User profile logs" +msgstr "Логи действий учётной записи" + +#: models.py:125 msgid "Avatar" msgstr "Аватар" -#: models.py:99 +#: models.py:127 msgid "Responsibility groups" msgstr "Группы администратора" -#: models.py:114 -msgid "Can view staff profile" -msgstr "Может просматривать учётку сотрудника" - -#: models.py:116 +#: models.py:141 msgid "Staff account profile" msgstr "Учётная запись работника" -#: models.py:117 +#: models.py:142 msgid "Staff account profiles" msgstr "Учётные записи работников" @@ -104,6 +160,22 @@ msgstr "Пользователи не найдены" msgid "Add account" msgstr "Добавить учётную запись" +#: templates/accounts/action_log.html:9 +msgid "Date" +msgstr "Дата" + +#: templates/accounts/action_log.html:10 +msgid "Additional" +msgstr "Дополнительное" + +#: templates/accounts/action_log.html:11 +msgid "Description" +msgstr "Описание" + +#: templates/accounts/action_log.html:24 +msgid "That admin has no logs" +msgstr "Эта учётная запись не имеет логов" + #: templates/accounts/create_acc.html:9 msgid "Add" msgstr "Добавить" @@ -145,7 +217,7 @@ msgstr "Повторите пароль" msgid "Save" msgstr "Сохранить" -#: templates/accounts/create_acc.html:92 templates/accounts/login.html:59 +#: templates/accounts/create_acc.html:92 #: templates/accounts/manage_responsibility_groups.html:21 #: templates/accounts/perms/perms_edit.html:68 #: templates/accounts/set_abon_groups_permission.html:21 @@ -153,8 +225,7 @@ msgstr "Сохранить" msgid "Reset" msgstr "Сбросить" -#: templates/accounts/index.html:13 templates/accounts/login.html:39 -#: templates/accounts/settings/ch_info.html:9 +#: templates/accounts/index.html:13 templates/accounts/settings/ch_info.html:9 #: templates/accounts/settings/ch_info.html:13 msgid "User name" msgstr "Логин" @@ -179,13 +250,13 @@ msgstr "Административный доступ (все права)" msgid "Auth" msgstr "Аутентификация" -#: templates/accounts/login.html:27 templates/accounts/login.html:56 +#: templates/accounts/login.html:27 templates/accounts/login.html:43 msgid "Login" msgstr "Войти" -#: templates/accounts/login.html:47 -msgid "Password" -msgstr "Пароль" +#: templates/accounts/login.html:48 +msgid "Login by location" +msgstr "Войти по местоположению" #: templates/accounts/manage_responsibility_groups.html:5 msgid "The responsibility of the administrator of the group of subscribers" @@ -235,50 +306,53 @@ msgstr "Старый пароль" msgid "New password" msgstr "Новый пароль" -#: views.py:47 +#: views.py:32 msgid "Wrong login or password, please try again" msgstr "Неправильный логин или пароль, попробуйте ещё раз" -#: views.py:123 +#: views.py:120 msgid "New password is empty, fill it" msgstr "Новый пароль пустой, придумайте себе пароль" -#: views.py:125 +#: views.py:122 msgid "Wrong password" msgstr "Неправильный пароль" -#: views.py:127 +#: views.py:124 msgid "Empty password, fill it" msgstr "Пустой пароль, впишите что-то в пароль" -#: views.py:150 +#: views.py:148 msgid "You forget specify a password for the new account" msgstr "Забыли указать пароль для нового аккаунта" -#: views.py:153 +#: views.py:151 msgid "You forget to repeat a password for the new account" msgstr "Забыли повторить пароль для нового аккаунта" -#: views.py:162 +#: views.py:160 msgid "Subscriber with this name already exist" msgstr "Пользователь с таким именем уже есть" -#: views.py:164 +#: views.py:162 msgid "Passwords does not match, try again" msgstr "Пароли не совпадают, попробуйте ещё раз" -#: views.py:179 +#: views.py:177 msgid "Profile has been deleted" msgstr "Учётная запись удалена" -#: views.py:252 +#: views.py:255 msgid "Permissions has successfully updated" msgstr "Права успешно обновлены" -#: views.py:318 +#: views.py:322 msgid "Responsibilities has been updated" msgstr "Ответственность за группы обновлена" +msgid "Password" +msgstr "Пароль" + msgid "Change self onfo" msgstr "Изменить инфу о себе" @@ -297,5 +371,5 @@ msgstr "Ответственность за группы" msgid "Profile" msgstr "Учётная запись" -msgid "Login by location" -msgstr "Войти по местоположению" +msgid "Action log" +msgstr "Лог действий" diff --git a/accounts_app/migrations/0003_new_user_profile_log.py b/accounts_app/migrations/0003_new_user_profile_log.py new file mode 100644 index 0000000..5988328 --- /dev/null +++ b/accounts_app/migrations/0003_new_user_profile_log.py @@ -0,0 +1,45 @@ +# Generated by Django 2.1 on 2018-08-26 19:52 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import jsonfield.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts_app', '0002_auto_20180807_1548'), + ] + + operations = [ + migrations.CreateModel( + name='UserProfileLog', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('meta_info', jsonfield.fields.JSONField(default=dict, verbose_name='Meta information')), + ('do_type', models.CharField(choices=[('cusr', 'Create user'), ('dusr', 'Delete user'), ('cdev', 'Create device'), ('ddev', 'Delete device'), ('cnas', 'Create NAS'), ('dnas', 'Delete NAS'), ('csrv', 'Create service'), ('dsrv', 'Delete service')], max_length=4, verbose_name='Action type')), + ('additional_text', models.CharField(blank=True, null=True, verbose_name='Additional info', max_length=512)), + ('action_date', models.DateTimeField(auto_now_add=True, verbose_name='Action date')), + ], + options={ + 'verbose_name': 'User profile log', + 'verbose_name_plural': 'User profile logs', + 'ordering': ('-action_date',), + }, + ), + migrations.AlterModelOptions( + name='userprofile', + options={'ordering': ('fio',), 'verbose_name': 'Staff account profile', 'verbose_name_plural': 'Staff account profiles'}, + ), + migrations.AlterField( + model_name='userprofile', + name='avatar', + field=models.ImageField(blank=True, default=None, null=True, upload_to='user/avatar', verbose_name='Avatar'), + ), + migrations.AddField( + model_name='userprofilelog', + name='account', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Author'), + ), + ] diff --git a/accounts_app/models.py b/accounts_app/models.py index d24d877..4bb8b6e 100644 --- a/accounts_app/models.py +++ b/accounts_app/models.py @@ -1,6 +1,8 @@ # -*- coding:utf-8 -*- import os from PIL import Image + +from jsonfield import JSONField from django.db import models from django.contrib.auth.models import BaseUserManager, AbstractBaseUser, PermissionsMixin from django.core.validators import RegexValidator @@ -85,7 +87,33 @@ class BaseAccount(AbstractBaseUser, PermissionsMixin): class Meta: db_table = 'base_accounts' - ordering = ('username',) + ordering = 'username', + + +class UserProfileLog(models.Model): + account = models.ForeignKey('UserProfile', on_delete=models.CASCADE, verbose_name=_('Author')) + meta_info = JSONField(verbose_name=_('Meta information')) + ACTION_TYPES = ( + ('cusr', _('Create user')), + ('dusr', _('Delete user')), + ('cdev', _('Create device')), + ('ddev', _('Delete device')), + ('cnas', _('Create NAS')), + ('dnas', _('Delete NAS')), + ('csrv', _('Create service')), + ('dsrv', _('Delete service')) + ) + do_type = models.CharField(_('Action type'), max_length=4, choices=ACTION_TYPES) + additional_text = models.CharField(_('Additional info'), blank=True, null=True, max_length=512) + action_date = models.DateTimeField(_('Action date'), auto_now_add=True) + + def __str__(self): + return self.get_do_type_display() + + class Meta: + ordering = '-action_date', + verbose_name = _('User profile log') + verbose_name_plural = _('User profile logs') class UserProfileManager(MyUserManager): @@ -110,12 +138,9 @@ class UserProfile(BaseAccount): return self.get_big_ava() class Meta: - permissions = ( - ('can_view_userprofile', _('Can view staff profile')), - ) verbose_name = _('Staff account profile') verbose_name_plural = _('Staff account profiles') - ordering = ('fio',) + ordering = 'fio', def _thumbnail_avatar(self): if self.avatar and os.path.isfile(self.avatar.path): @@ -127,3 +152,24 @@ class UserProfile(BaseAccount): r = super().save(*args, **kwargs) self._thumbnail_avatar() return r + + def log(self, request_meta: dict, do_type: str, additional_text=None) -> None: + """ + Make log about administrator actions. + :param request_meta: META from django request. + :param do_type: Choice from UserProfileLog.ACTION_TYPES + :param additional_text: Additional information for action + :return: None + """ + inf = { + 'src_ip': request_meta.get('REMOTE_ADDR'), + 'username': request_meta.get('USER'), + 'hostname': request_meta.get('HOSTNAME'), + 'useragent': request_meta.get('HTTP_USER_AGENT') + } + UserProfileLog.objects.create( + account=self, + meta_info=inf, + do_type=do_type, + additional_text=additional_text + ) diff --git a/accounts_app/templates/accounts/action_log.html b/accounts_app/templates/accounts/action_log.html new file mode 100644 index 0000000..0c442e8 --- /dev/null +++ b/accounts_app/templates/accounts/action_log.html @@ -0,0 +1,29 @@ +{% extends request.is_ajax|yesno:'nullcont.htm,accounts/ext.htm' %} +{% load i18n %} + +{% block content %} +
+ + + + + + + + + + + {% for log in object_list %} + + + + + + + {% empty %} + + {% endfor %} + +
{% trans 'Date' %}{% trans 'Additional' %}{% trans 'Description' %}{% trans 'Meta information' %}
{{ log.action_date|date:'D d E Y H:i:s' }}{{ log.additional_text|default_if_none:'-' }}{{ log.get_do_type_display }}{{ log.meta_info }}
{% trans 'That admin has no logs' %}
+
+{% endblock %} diff --git a/accounts_app/templates/accounts/ext.htm b/accounts_app/templates/accounts/ext.htm index 618cc04..cb2817d 100644 --- a/accounts_app/templates/accounts/ext.htm +++ b/accounts_app/templates/accounts/ext.htm @@ -70,6 +70,17 @@ {% endif %} + + {% if perms.accounts_app.view_userprofilelog %} + {% url 'acc_app:action_log' uid as actlog %} + + + + {% trans 'Action log' %} + + + {% endif %} +
diff --git a/accounts_app/urls.py b/accounts_app/urls.py index 9f37d72..6e0484a 100644 --- a/accounts_app/urls.py +++ b/accounts_app/urls.py @@ -37,5 +37,7 @@ urlpatterns = [ path('/manage_responsibility_groups/', views.ManageResponsibilityGroups.as_view(), - name='manage_responsibility_groups') + name='manage_responsibility_groups'), + + path('/actions/', views.ActionListView.as_view(), name='action_log') ] diff --git a/accounts_app/views.py b/accounts_app/views.py index 10006bc..70c6747 100644 --- a/accounts_app/views.py +++ b/accounts_app/views.py @@ -14,7 +14,7 @@ from django.conf import settings from group_app.models import Group -from .models import UserProfile +from .models import UserProfile, UserProfileLog from .forms import AvatarChangeForm from djing import lib from djing.lib.decorators import only_admins @@ -67,7 +67,7 @@ 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): + if request.user != usr and not request.user.has_perm('accounts_app.view_userprofile', usr): raise PermissionDenied if request.method == 'POST': usr.username = request.POST.get('username') @@ -168,7 +168,7 @@ def create_profile(request): @login_required @only_admins -def delete_profile(request, uid): +def delete_profile(request, uid: int): prf = get_object_or_404(UserProfile, id=uid) if uid != request.user.id: if not request.user.has_perm('acc_app.delete_userprofile', prf): @@ -187,12 +187,12 @@ class AccountsListView(ListView): def get_queryset(self): users = UserProfile.objects.filter(is_admin=True).exclude(pk=self.request.user.pk) - users = get_objects_for_user(self.request.user, 'accounts_app.can_view_userprofile', users) + users = get_objects_for_user(self.request.user, 'accounts_app.view_userprofile', users) return users @login_required -def perms(request, uid): +def perms(request, uid: int): if not request.user.is_superuser: raise PermissionDenied userprofile = get_object_or_404(UserProfile, id=uid) @@ -239,7 +239,7 @@ class PermissionClassListView(ListView): @login_required @only_admins -def perms_edit(request, uid, klass_name, obj_id): +def perms_edit(request, uid: int, klass_name, obj_id): if not request.user.is_superuser: raise PermissionDenied from django.apps import apps @@ -265,7 +265,7 @@ def perms_edit(request, uid, klass_name, obj_id): @login_required @only_admins -def set_abon_groups_permission(request, uid): +def set_abon_groups_permission(request, uid: int): # Only superuser can change object permissions if not request.user.is_superuser: raise PermissionDenied @@ -321,3 +321,21 @@ class ManageResponsibilityGroups(ListView): profile.responsibility_groups.add(*checked_groups) messages.success(request, _('Responsibilities has been updated')) return HttpResponseRedirect(self.get_success_url()) + + +@method_decorator(login_decs, name='dispatch') +@method_decorator(permission_required('accounts_app.view_userprofilelog'), name='dispatch') +class ActionListView(ListView): + paginate_by = getattr(settings, 'PAGINATION_ITEMS_PER_PAGE', 10) + template_name = 'accounts/action_log.html' + model = UserProfileLog + + def get_queryset(self): + uid = self.kwargs.get('uid') + return UserProfileLog.objects.filter(account__id=uid) + + def get_context_data(self, **kwargs): + context = super(ActionListView, self).get_context_data(**kwargs) + context['uid'] = self.kwargs.get('uid') + context['userprofile'] = UserProfile.objects.get(pk=context['uid']) + return context diff --git a/devapp/forms.py b/devapp/forms.py index 14b5961..e82cff3 100644 --- a/devapp/forms.py +++ b/devapp/forms.py @@ -22,7 +22,8 @@ class DeviceForm(forms.ModelForm): else: comment = None super(DeviceForm, self).__init__(*args, **kwargs) - self.fields['comment'].widget.attrs['placeholder'] = comment + if comment: + self.fields['comment'].widget.attrs['placeholder'] = comment mac_addr = forms.CharField(widget=forms.TextInput(attrs={ 'pattern': MAC_ADDR_REGEX, diff --git a/devapp/views.py b/devapp/views.py index f0a2ad8..6f46ab6 100644 --- a/devapp/views.py +++ b/devapp/views.py @@ -79,8 +79,13 @@ class DeviceDeleteView(DeleteView): def delete(self, request, *args, **kwargs): res = super().delete(request, *args, **kwargs) try: + request.user.log(request.META, 'ddev', 'ip %s, mac: %s, "%s"' % ( + self.object.ip_address or '-', + self.object.mac_addr or '-', + self.object.comment or '-' + )) self.object.update_dhcp() - except DeviceDBException as e: + except (DeviceDBException, PermissionError) as e: messages.error(request, e) messages.success(request, _('Device successfully deleted')) return res @@ -183,6 +188,11 @@ class DeviceCreateView(CreateView): r = super().form_valid(form) # change device info in dhcpd.conf try: + self.request.user.log(self.request.META, 'cdev', 'ip %s, mac: %s, "%s"' % ( + self.object.ip_address, + self.object.mac_addr, + self.object.comment + )) self.object.update_dhcp() messages.success(self.request, _('Device info has been saved')) except PermissionError as e: diff --git a/msg_app/templates/msg_app/chat.html b/msg_app/templates/msg_app/chat.html index 57f7ad3..48a4488 100644 --- a/msg_app/templates/msg_app/chat.html +++ b/msg_app/templates/msg_app/chat.html @@ -25,7 +25,7 @@
- {% with can_view_profile=perms.accounts_app.can_view_userprofile %} + {% with can_view_profile=perms.accounts_app.view_userprofile %} {% for msg in msg_list %} {% with author=msg.author %}
diff --git a/nas_app/views.py b/nas_app/views.py index b47a5c0..5bd0a21 100644 --- a/nas_app/views.py +++ b/nas_app/views.py @@ -34,6 +34,11 @@ class NasCreateView(CreateView): r = super(NasCreateView, self).form_valid(form) assign_perm("nas_app.change_nasmodel", self.request.user, self.object) assign_perm("nas_app.can_view_nas", self.request.user, self.object) + self.request.user.log(self.request.META, 'cnas', '"%(title)s", %(ip)s, %(type)s' % { + 'title': self.object.title, + 'ip': self.object.ip_address, + 'type': self.object.get_nas_type_display() + }) messages.success(self.request, _('New NAS has been created')) return r @@ -48,6 +53,11 @@ class NasDeleteView(DeleteView): def delete(self, request, *args, **kwargs): try: r = super(NasDeleteView, self).delete(request, *args, **kwargs) + request.user.log(request.META, 'dnas', '"%(title)s", %(ip)s, %(type)s' % { + 'title': self.object.title, + 'ip': self.object.ip_address, + 'type': self.object.get_nas_type_display() + }) messages.success(request, _('Server successfully removed')) return r except MessageFailure as e: diff --git a/tariff_app/views.py b/tariff_app/views.py index 665ba76..ba3e33b 100644 --- a/tariff_app/views.py +++ b/tariff_app/views.py @@ -47,9 +47,15 @@ def edit_tarif(request, tarif_id=0): if request.method == 'POST': frm = forms.TariffForm(request.POST, instance=tarif) if frm.is_valid(): - new_service = frm.save() + service = frm.save() + if tarif is None: + request.user.log(request.META, 'csrv', '"%(title)s", "%(descr)s", %(amount).2f' % { + 'title': service.title or '-', + 'descr': service.descr or '-', + 'amount': service.amount or 0.0 + }) messages.success(request, _('Service has been saved')) - return redirect('tarifs:edit', tarif_id=new_service.pk) + return redirect('tarifs:edit', tarif_id=service.pk) else: messages.warning(request, _('Some fields were filled incorrect, please try again')) else: @@ -70,6 +76,11 @@ class TariffDeleteView(DeleteView): def delete(self, request, *args, **kwargs): res = super().delete(request, *args, **kwargs) + request.user.log(request.META, 'dsrv', '"%(title)s", "%(descr)s", %(amount).2f' % { + 'title': self.object.title or '-', + 'descr': self.object.descr or '-', + 'amount': self.object.amount or 0.0 + }) messages.success(request, _('Service has been deleted')) return res