mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-30 03:56:23 -05:00 
			
		
		
		
	Bulk edit permissions
This commit is contained in:
		| @@ -108,6 +108,7 @@ import localeSr from '@angular/common/locales/sr' | |||||||
| import localeSv from '@angular/common/locales/sv' | import localeSv from '@angular/common/locales/sv' | ||||||
| import localeTr from '@angular/common/locales/tr' | import localeTr from '@angular/common/locales/tr' | ||||||
| import localeZh from '@angular/common/locales/zh' | import localeZh from '@angular/common/locales/zh' | ||||||
|  | import { PermissionsDialogComponent } from './components/common/permissions-dialog/permissions-dialog.component' | ||||||
|  |  | ||||||
| registerLocaleData(localeBe) | registerLocaleData(localeBe) | ||||||
| registerLocaleData(localeCs) | registerLocaleData(localeCs) | ||||||
| @@ -203,6 +204,7 @@ function initializeApp(settings: SettingsService) { | |||||||
|     PermissionsGroupComponent, |     PermissionsGroupComponent, | ||||||
|     IfOwnerDirective, |     IfOwnerDirective, | ||||||
|     IfObjectPermissionsDirective, |     IfObjectPermissionsDirective, | ||||||
|  |     PermissionsDialogComponent, | ||||||
|   ], |   ], | ||||||
|   imports: [ |   imports: [ | ||||||
|     BrowserModule, |     BrowserModule, | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| import { Component, EventEmitter, Input, Output } from '@angular/core' | import { Component, EventEmitter, Input, Output } from '@angular/core' | ||||||
| import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap' | import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap' | ||||||
| import { interval, Subject, switchMap, take } from 'rxjs' | import { interval, Subject, take } from 'rxjs' | ||||||
|  |  | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-confirm-dialog', |   selector: 'app-confirm-dialog', | ||||||
|   | |||||||
| @@ -0,0 +1,29 @@ | |||||||
|  | <div class="modal-header"> | ||||||
|  |     <h4 class="modal-title" id="modal-basic-title">{{title}}</h4> | ||||||
|  |     <button type="button" class="btn-close" aria-label="Close" (click)="cancelClicked()"> | ||||||
|  |     </button> | ||||||
|  |   </div> | ||||||
|  |   <div class="modal-body"> | ||||||
|  |  | ||||||
|  |     <p class="mb-0" *ngIf="message" [innerHTML]="message | safeHtml"></p> | ||||||
|  |  | ||||||
|  |     <form [formGroup]="form"> | ||||||
|  |       <div formGroupName="set_permissions"> | ||||||
|  |         <h6 i18n>View</h6> | ||||||
|  |         <div formGroupName="view"> | ||||||
|  |           <app-permissions-user type="view" formControlName="users"></app-permissions-user> | ||||||
|  |           <app-permissions-group type="view" formControlName="groups"></app-permissions-group> | ||||||
|  |         </div> | ||||||
|  |         <h6 i18n>Edit</h6> | ||||||
|  |         <div formGroupName="change"> | ||||||
|  |           <app-permissions-user type="change" formControlName="users"></app-permissions-user> | ||||||
|  |           <app-permissions-group type="change" formControlName="groups"></app-permissions-group> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </form> | ||||||
|  |  | ||||||
|  |   </div> | ||||||
|  |   <div class="modal-footer"> | ||||||
|  |     <button type="button" class="btn btn-outline-primary" (click)="cancelClicked()" i18n>Cancel</button> | ||||||
|  |     <button type="button" class="btn btn-primary" (click)="confirmClicked.emit(permissions)" i18n>Confirm</button> | ||||||
|  |   </div> | ||||||
| @@ -0,0 +1,46 @@ | |||||||
|  | import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core' | ||||||
|  | import { FormControl, FormGroup } from '@angular/forms' | ||||||
|  | import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap' | ||||||
|  | import { PaperlessGroup } from 'src/app/data/paperless-group' | ||||||
|  | import { PaperlessUser } from 'src/app/data/paperless-user' | ||||||
|  |  | ||||||
|  | @Component({ | ||||||
|  |   selector: 'app-permissions-dialog', | ||||||
|  |   templateUrl: './permissions-dialog.component.html', | ||||||
|  |   styleUrls: ['./permissions-dialog.component.scss'], | ||||||
|  | }) | ||||||
|  | export class PermissionsDialogComponent implements OnInit { | ||||||
|  |   constructor(public activeModal: NgbActiveModal) {} | ||||||
|  |  | ||||||
|  |   @Output() | ||||||
|  |   public confirmClicked = new EventEmitter() | ||||||
|  |  | ||||||
|  |   @Input() | ||||||
|  |   title = $localize`Set Permissions` | ||||||
|  |  | ||||||
|  |   form = new FormGroup({ | ||||||
|  |     set_permissions: new FormGroup({ | ||||||
|  |       view: new FormGroup({ | ||||||
|  |         users: new FormControl([]), | ||||||
|  |         groups: new FormControl([]), | ||||||
|  |       }), | ||||||
|  |       change: new FormGroup({ | ||||||
|  |         users: new FormControl([]), | ||||||
|  |         groups: new FormControl([]), | ||||||
|  |       }), | ||||||
|  |     }), | ||||||
|  |   }) | ||||||
|  |  | ||||||
|  |   get permissions() { | ||||||
|  |     return this.form.value['set_permissions'] | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @Input() | ||||||
|  |   message = $localize`Note that permissions set here will override any existing permissions` | ||||||
|  |  | ||||||
|  |   ngOnInit(): void {} | ||||||
|  |  | ||||||
|  |   cancelClicked() { | ||||||
|  |     this.activeModal.close() | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -65,7 +65,13 @@ | |||||||
|     </div> |     </div> | ||||||
|   </div> |   </div> | ||||||
|   <div class="col-auto ms-auto mb-2 mb-xl-0 d-flex"> |   <div class="col-auto ms-auto mb-2 mb-xl-0 d-flex"> | ||||||
|     <div class="btn-group btn-group-sm me-2"> |     <div class="btn-toolbar me-2"> | ||||||
|  |  | ||||||
|  |       <button type="button" class="btn btn-sm btn-outline-primary me-2" (click)="setPermissions()"> | ||||||
|  |         <svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor"> | ||||||
|  |           <use xlink:href="assets/bootstrap-icons.svg#person-fill-lock" /> | ||||||
|  |         </svg> <ng-container i18n>Permissions</ng-container> | ||||||
|  |       </button> | ||||||
|  |  | ||||||
|       <div ngbDropdown class="me-2 d-flex"> |       <div ngbDropdown class="me-2 d-flex"> | ||||||
|         <button class="btn btn-sm btn-outline-primary" id="dropdownSelect" ngbDropdownToggle> |         <button class="btn btn-sm btn-outline-primary" id="dropdownSelect" ngbDropdownToggle> | ||||||
|   | |||||||
| @@ -26,6 +26,7 @@ import { StoragePathService } from 'src/app/services/rest/storage-path.service' | |||||||
| import { PaperlessStoragePath } from 'src/app/data/paperless-storage-path' | import { PaperlessStoragePath } from 'src/app/data/paperless-storage-path' | ||||||
| import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings' | import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings' | ||||||
| import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component' | import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component' | ||||||
|  | import { PermissionsDialogComponent } from '../../common/permissions-dialog/permissions-dialog.component' | ||||||
|  |  | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-bulk-editor', |   selector: 'app-bulk-editor', | ||||||
| @@ -397,4 +398,16 @@ export class BulkEditorComponent extends ComponentWithPermissions { | |||||||
|       this.executeBulkOperation(modal, 'redo_ocr', {}) |       this.executeBulkOperation(modal, 'redo_ocr', {}) | ||||||
|     }) |     }) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   setPermissions() { | ||||||
|  |     let modal = this.modalService.open(PermissionsDialogComponent, { | ||||||
|  |       backdrop: 'static', | ||||||
|  |     }) | ||||||
|  |     modal.componentInstance.confirmClicked.subscribe((permissions) => { | ||||||
|  |       modal.componentInstance.buttonsEnabled = false | ||||||
|  |       this.executeBulkOperation(modal, 'set_permissions', { | ||||||
|  |         permissions, | ||||||
|  |       }) | ||||||
|  |     }) | ||||||
|  |   } | ||||||
| } | } | ||||||
|   | |||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| Before Width: | Height: | Size: 861 KiB After Width: | Height: | Size: 1.0 MiB | 
| @@ -5,6 +5,7 @@ from documents.models import Correspondent | |||||||
| from documents.models import Document | from documents.models import Document | ||||||
| from documents.models import DocumentType | from documents.models import DocumentType | ||||||
| from documents.models import StoragePath | from documents.models import StoragePath | ||||||
|  | from documents.permissions import set_permissions_for_object | ||||||
| from documents.tasks import bulk_update_documents | from documents.tasks import bulk_update_documents | ||||||
| from documents.tasks import update_document_archive_file | from documents.tasks import update_document_archive_file | ||||||
|  |  | ||||||
| @@ -128,3 +129,15 @@ def redo_ocr(doc_ids): | |||||||
|         ) |         ) | ||||||
|  |  | ||||||
|     return "OK" |     return "OK" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def set_permissions(doc_ids, permissions): | ||||||
|  |  | ||||||
|  |     qs = Document.objects.filter(id__in=doc_ids) | ||||||
|  |     for doc in qs: | ||||||
|  |         set_permissions_for_object(permissions, doc) | ||||||
|  |     affected_docs = [doc.id for doc in qs] | ||||||
|  |  | ||||||
|  |     bulk_update_documents.delay(document_ids=affected_docs) | ||||||
|  |  | ||||||
|  |     return "OK" | ||||||
|   | |||||||
| @@ -1,3 +1,11 @@ | |||||||
|  | from django.contrib.auth.models import Group | ||||||
|  | from django.contrib.auth.models import Permission | ||||||
|  | from django.contrib.auth.models import User | ||||||
|  | from django.contrib.contenttypes.models import ContentType | ||||||
|  | from guardian.models import GroupObjectPermission | ||||||
|  | from guardian.shortcuts import assign_perm | ||||||
|  | from guardian.shortcuts import get_users_with_perms | ||||||
|  | from guardian.shortcuts import remove_perm | ||||||
| from rest_framework.permissions import BasePermission | from rest_framework.permissions import BasePermission | ||||||
| from rest_framework.permissions import DjangoObjectPermissions | from rest_framework.permissions import DjangoObjectPermissions | ||||||
|  |  | ||||||
| @@ -31,3 +39,65 @@ class PaperlessObjectPermissions(DjangoObjectPermissions): | |||||||
| class PaperlessAdminPermissions(BasePermission): | class PaperlessAdminPermissions(BasePermission): | ||||||
|     def has_permission(self, request, view): |     def has_permission(self, request, view): | ||||||
|         return request.user.has_perm("admin.view_logentry") |         return request.user.has_perm("admin.view_logentry") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def get_groups_with_only_permission(obj, codename): | ||||||
|  |     ctype = ContentType.objects.get_for_model(obj) | ||||||
|  |     permission = Permission.objects.get(content_type=ctype, codename=codename) | ||||||
|  |     group_object_perm_group_ids = ( | ||||||
|  |         GroupObjectPermission.objects.filter( | ||||||
|  |             object_pk=obj.pk, | ||||||
|  |             content_type=ctype, | ||||||
|  |         ) | ||||||
|  |         .filter(permission=permission) | ||||||
|  |         .values_list("group_id") | ||||||
|  |     ) | ||||||
|  |     return Group.objects.filter(id__in=group_object_perm_group_ids).distinct() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def set_permissions_for_object(permissions, object): | ||||||
|  |     print(permissions, object) | ||||||
|  |     for action in permissions: | ||||||
|  |         permission = f"{action}_{object.__class__.__name__.lower()}" | ||||||
|  |         # users | ||||||
|  |         users_to_add = User.objects.filter(id__in=permissions[action]["users"]) | ||||||
|  |         users_to_remove = get_users_with_perms( | ||||||
|  |             object, | ||||||
|  |             only_with_perms_in=[permission], | ||||||
|  |         ) | ||||||
|  |         if len(users_to_add) > 0 and len(users_to_remove) > 0: | ||||||
|  |             users_to_remove = users_to_remove.difference(users_to_add) | ||||||
|  |         if len(users_to_remove) > 0: | ||||||
|  |             for user in users_to_remove: | ||||||
|  |                 remove_perm(permission, user, object) | ||||||
|  |         if len(users_to_add) > 0: | ||||||
|  |             for user in users_to_add: | ||||||
|  |                 assign_perm(permission, user, object) | ||||||
|  |                 if action == "change": | ||||||
|  |                     # change gives view too | ||||||
|  |                     assign_perm( | ||||||
|  |                         f"view_{object.__class__.__name__.lower()}", | ||||||
|  |                         user, | ||||||
|  |                         object, | ||||||
|  |                     ) | ||||||
|  |         # groups | ||||||
|  |         groups_to_add = Group.objects.filter(id__in=permissions[action]["groups"]) | ||||||
|  |         groups_to_remove = get_groups_with_only_permission( | ||||||
|  |             object, | ||||||
|  |             permission, | ||||||
|  |         ) | ||||||
|  |         if len(groups_to_add) > 0 and len(groups_to_remove) > 0: | ||||||
|  |             groups_to_remove = groups_to_remove.difference(groups_to_add) | ||||||
|  |         if len(groups_to_remove) > 0: | ||||||
|  |             for group in groups_to_remove: | ||||||
|  |                 remove_perm(permission, group, object) | ||||||
|  |         if len(groups_to_add) > 0: | ||||||
|  |             for group in groups_to_add: | ||||||
|  |                 assign_perm(permission, group, object) | ||||||
|  |                 if action == "change": | ||||||
|  |                     # change gives view too | ||||||
|  |                     assign_perm( | ||||||
|  |                         f"view_{object.__class__.__name__.lower()}", | ||||||
|  |                         group, | ||||||
|  |                         object, | ||||||
|  |                     ) | ||||||
|   | |||||||
| @@ -28,16 +28,13 @@ from .models import UiSettings | |||||||
| from .models import PaperlessTask | from .models import PaperlessTask | ||||||
| from .parsers import is_mime_type_supported | from .parsers import is_mime_type_supported | ||||||
|  |  | ||||||
| from guardian.models import GroupObjectPermission |  | ||||||
| from guardian.shortcuts import assign_perm |  | ||||||
| from guardian.shortcuts import remove_perm |  | ||||||
| from guardian.shortcuts import get_users_with_perms | from guardian.shortcuts import get_users_with_perms | ||||||
|  |  | ||||||
| from django.contrib.contenttypes.models import ContentType |  | ||||||
|  |  | ||||||
| from django.contrib.auth.models import User | from django.contrib.auth.models import User | ||||||
| from django.contrib.auth.models import Group | from django.contrib.auth.models import Group | ||||||
| from django.contrib.auth.models import Permission |  | ||||||
|  | from documents.permissions import get_groups_with_only_permission | ||||||
|  | from documents.permissions import set_permissions_for_object | ||||||
|  |  | ||||||
|  |  | ||||||
| # https://www.django-rest-framework.org/api-guide/serializers/#example | # https://www.django-rest-framework.org/api-guide/serializers/#example | ||||||
| @@ -85,56 +82,7 @@ class MatchingModelSerializer(serializers.ModelSerializer): | |||||||
|         return match |         return match | ||||||
|  |  | ||||||
|  |  | ||||||
| def get_groups_with_only_permission(obj, codename): | class SetPermissionsMixin: | ||||||
|     ctype = ContentType.objects.get_for_model(obj) |  | ||||||
|     permission = Permission.objects.get(content_type=ctype, codename=codename) |  | ||||||
|     group_object_perm_group_ids = ( |  | ||||||
|         GroupObjectPermission.objects.filter( |  | ||||||
|             object_pk=obj.pk, |  | ||||||
|             content_type=ctype, |  | ||||||
|         ) |  | ||||||
|         .filter(permission=permission) |  | ||||||
|         .values_list("group_id") |  | ||||||
|     ) |  | ||||||
|     return Group.objects.filter(id__in=group_object_perm_group_ids).distinct() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class OwnedObjectSerializer(serializers.ModelSerializer): |  | ||||||
|     def get_permissions(self, obj): |  | ||||||
|         view_codename = f"view_{obj.__class__.__name__.lower()}" |  | ||||||
|         change_codename = f"change_{obj.__class__.__name__.lower()}" |  | ||||||
|         return { |  | ||||||
|             "view": { |  | ||||||
|                 "users": get_users_with_perms( |  | ||||||
|                     obj, |  | ||||||
|                     only_with_perms_in=[view_codename], |  | ||||||
|                 ).values_list("id", flat=True), |  | ||||||
|                 "groups": get_groups_with_only_permission( |  | ||||||
|                     obj, |  | ||||||
|                     codename=view_codename, |  | ||||||
|                 ).values_list("id", flat=True), |  | ||||||
|             }, |  | ||||||
|             "change": { |  | ||||||
|                 "users": get_users_with_perms( |  | ||||||
|                     obj, |  | ||||||
|                     only_with_perms_in=[change_codename], |  | ||||||
|                 ).values_list("id", flat=True), |  | ||||||
|                 "groups": get_groups_with_only_permission( |  | ||||||
|                     obj, |  | ||||||
|                     codename=change_codename, |  | ||||||
|                 ).values_list("id", flat=True), |  | ||||||
|             }, |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|     permissions = SerializerMethodField(read_only=True) |  | ||||||
|  |  | ||||||
|     set_permissions = serializers.DictField( |  | ||||||
|         label="Set permissions", |  | ||||||
|         allow_empty=True, |  | ||||||
|         required=False, |  | ||||||
|         write_only=True, |  | ||||||
|     ) |  | ||||||
|  |  | ||||||
|     def _validate_user_ids(self, user_ids): |     def _validate_user_ids(self, user_ids): | ||||||
|         users = User.objects.none() |         users = User.objects.none() | ||||||
|         if user_ids is not None: |         if user_ids is not None: | ||||||
| @@ -169,52 +117,55 @@ class OwnedObjectSerializer(serializers.ModelSerializer): | |||||||
|         if set_permissions is not None: |         if set_permissions is not None: | ||||||
|             for action in permissions_dict: |             for action in permissions_dict: | ||||||
|                 users = set_permissions[action]["users"] |                 users = set_permissions[action]["users"] | ||||||
|                 permissions_dict[action]["users"] = self._validate_user_ids(users) |                 self._validate_user_ids(users) | ||||||
|                 groups = set_permissions[action]["groups"] |                 groups = set_permissions[action]["groups"] | ||||||
|                 permissions_dict[action]["groups"] = self._validate_group_ids(groups) |                 self._validate_group_ids(groups) | ||||||
|         return permissions_dict |         return permissions_dict | ||||||
|  |  | ||||||
|  |     def _set_permissions(self, permissions, object): | ||||||
|  |         set_permissions_for_object(permissions, object) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class OwnedObjectSerializer(serializers.ModelSerializer, SetPermissionsMixin): | ||||||
|     def __init__(self, *args, **kwargs): |     def __init__(self, *args, **kwargs): | ||||||
|         self.user = kwargs.pop("user", None) |         self.user = kwargs.pop("user", None) | ||||||
|         return super().__init__(*args, **kwargs) |         return super().__init__(*args, **kwargs) | ||||||
|  |  | ||||||
|     def _set_permissions(self, permissions, object): |     def get_permissions(self, obj): | ||||||
|         for action in permissions: |         view_codename = f"view_{obj.__class__.__name__.lower()}" | ||||||
|             permission = f"{action}_{object.__class__.__name__.lower()}" |         change_codename = f"change_{obj.__class__.__name__.lower()}" | ||||||
|             # users |         return { | ||||||
|             users_to_add = permissions[action]["users"] |             "view": { | ||||||
|             users_to_remove = get_users_with_perms( |                 "users": get_users_with_perms( | ||||||
|                 object, |                     obj, | ||||||
|                 only_with_perms_in=[permission], |                     only_with_perms_in=[view_codename], | ||||||
|             ).difference(users_to_add) |                 ).values_list("id", flat=True), | ||||||
|             for user in users_to_remove: |                 "groups": get_groups_with_only_permission( | ||||||
|                 remove_perm(permission, user, object) |                     obj, | ||||||
|             for user in users_to_add: |                     codename=view_codename, | ||||||
|                 assign_perm(permission, user, object) |                 ).values_list("id", flat=True), | ||||||
|                 if action == "change": |             }, | ||||||
|                     # change gives view too |             "change": { | ||||||
|                     assign_perm( |                 "users": get_users_with_perms( | ||||||
|                         f"view_{object.__class__.__name__.lower()}", |                     obj, | ||||||
|                         user, |                     only_with_perms_in=[change_codename], | ||||||
|                         object, |                 ).values_list("id", flat=True), | ||||||
|                     ) |                 "groups": get_groups_with_only_permission( | ||||||
|             # groups |                     obj, | ||||||
|             groups_to_add = permissions[action]["groups"] |                     codename=change_codename, | ||||||
|             groups_to_remove = get_groups_with_only_permission( |                 ).values_list("id", flat=True), | ||||||
|                 object, |             }, | ||||||
|                 permission, |         } | ||||||
|             ).difference(groups_to_add) |  | ||||||
|             for group in groups_to_remove: |     permissions = SerializerMethodField(read_only=True) | ||||||
|                 remove_perm(permission, group, object) |  | ||||||
|             for group in groups_to_add: |     set_permissions = serializers.DictField( | ||||||
|                 assign_perm(permission, group, object) |         label="Set permissions", | ||||||
|                 if action == "change": |         allow_empty=True, | ||||||
|                     # change gives view too |         required=False, | ||||||
|                     assign_perm( |         write_only=True, | ||||||
|                         f"view_{object.__class__.__name__.lower()}", |  | ||||||
|                         group, |  | ||||||
|                         object, |  | ||||||
|     ) |     ) | ||||||
|  |     # other methods in mixin | ||||||
|  |  | ||||||
|     def create(self, validated_data): |     def create(self, validated_data): | ||||||
|         if self.user and ( |         if self.user and ( | ||||||
| @@ -515,7 +466,7 @@ class DocumentListSerializer(serializers.Serializer): | |||||||
|         return documents |         return documents | ||||||
|  |  | ||||||
|  |  | ||||||
| class BulkEditSerializer(DocumentListSerializer): | class BulkEditSerializer(DocumentListSerializer, SetPermissionsMixin): | ||||||
|  |  | ||||||
|     method = serializers.ChoiceField( |     method = serializers.ChoiceField( | ||||||
|         choices=[ |         choices=[ | ||||||
| @@ -527,6 +478,7 @@ class BulkEditSerializer(DocumentListSerializer): | |||||||
|             "modify_tags", |             "modify_tags", | ||||||
|             "delete", |             "delete", | ||||||
|             "redo_ocr", |             "redo_ocr", | ||||||
|  |             "set_permissions", | ||||||
|         ], |         ], | ||||||
|         label="Method", |         label="Method", | ||||||
|         write_only=True, |         write_only=True, | ||||||
| @@ -562,6 +514,8 @@ class BulkEditSerializer(DocumentListSerializer): | |||||||
|             return bulk_edit.delete |             return bulk_edit.delete | ||||||
|         elif method == "redo_ocr": |         elif method == "redo_ocr": | ||||||
|             return bulk_edit.redo_ocr |             return bulk_edit.redo_ocr | ||||||
|  |         elif method == "set_permissions": | ||||||
|  |             return bulk_edit.set_permissions | ||||||
|         else: |         else: | ||||||
|             raise serializers.ValidationError("Unsupported method.") |             raise serializers.ValidationError("Unsupported method.") | ||||||
|  |  | ||||||
| @@ -625,6 +579,12 @@ class BulkEditSerializer(DocumentListSerializer): | |||||||
|         else: |         else: | ||||||
|             raise serializers.ValidationError("remove_tags not specified") |             raise serializers.ValidationError("remove_tags not specified") | ||||||
|  |  | ||||||
|  |     def _validate_parameters_set_permissions(self, parameters): | ||||||
|  |         if "permissions" in parameters: | ||||||
|  |             self.validate_set_permissions(parameters["permissions"]) | ||||||
|  |         else: | ||||||
|  |             raise serializers.ValidationError("permissions not specified") | ||||||
|  |  | ||||||
|     def validate(self, attrs): |     def validate(self, attrs): | ||||||
|  |  | ||||||
|         method = attrs["method"] |         method = attrs["method"] | ||||||
| @@ -640,6 +600,8 @@ class BulkEditSerializer(DocumentListSerializer): | |||||||
|             self._validate_parameters_modify_tags(parameters) |             self._validate_parameters_modify_tags(parameters) | ||||||
|         elif method == bulk_edit.set_storage_path: |         elif method == bulk_edit.set_storage_path: | ||||||
|             self._validate_storage_path(parameters) |             self._validate_storage_path(parameters) | ||||||
|  |         elif method == bulk_edit.set_permissions: | ||||||
|  |             self._validate_parameters_set_permissions(parameters) | ||||||
|  |  | ||||||
|         return attrs |         return attrs | ||||||
|  |  | ||||||
|   | |||||||
| @@ -41,6 +41,8 @@ from paperless import version | |||||||
| from rest_framework.test import APITestCase | from rest_framework.test import APITestCase | ||||||
| from whoosh.writing import AsyncWriter | from whoosh.writing import AsyncWriter | ||||||
|  |  | ||||||
|  | from guardian.shortcuts import get_users_with_perms | ||||||
|  |  | ||||||
|  |  | ||||||
| class TestDocumentApi(DirectoriesMixin, APITestCase): | class TestDocumentApi(DirectoriesMixin, APITestCase): | ||||||
|     def setUp(self): |     def setUp(self): | ||||||
| @@ -2329,6 +2331,31 @@ class TestBulkEdit(DirectoriesMixin, APITestCase): | |||||||
|             ], |             ], | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|  |     def test_set_permissions(self): | ||||||
|  |         user1 = User.objects.create(username="user1") | ||||||
|  |         user2 = User.objects.create(username="user2") | ||||||
|  |         permissions = { | ||||||
|  |             "view": { | ||||||
|  |                 "users": User.objects.filter(id__in=[user1.id, user2.id]), | ||||||
|  |                 "groups": Group.objects.none(), | ||||||
|  |             }, | ||||||
|  |             "change": { | ||||||
|  |                 "users": User.objects.filter(id__in=[user1.id]), | ||||||
|  |                 "groups": Group.objects.none(), | ||||||
|  |             }, | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         bulk_edit.set_permissions( | ||||||
|  |             [self.doc2.id, self.doc3.id], | ||||||
|  |             permissions=permissions, | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         self.assertEqual(get_users_with_perms(self.doc2).count(), 2) | ||||||
|  |  | ||||||
|  |         self.async_task.assert_called_once() | ||||||
|  |         args, kwargs = self.async_task.call_args | ||||||
|  |         self.assertCountEqual(kwargs["document_ids"], [self.doc2.id, self.doc3.id]) | ||||||
|  |  | ||||||
|  |  | ||||||
| class TestBulkDownload(DirectoriesMixin, APITestCase): | class TestBulkDownload(DirectoriesMixin, APITestCase): | ||||||
|     def setUp(self): |     def setUp(self): | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Michael Shamoon
					Michael Shamoon