</div>
</div>
<div class="col-auto ms-auto mb-2 mb-xl-0 d-flex">
- <div class="btn-toolbar me-2">
+ <div class="btn-group btn-group-sm me-2">
+
+ <button type="button" class="btn btn-sm btn-outline-primary me-2" (click)="setPermissions()" [disabled]="!userOwnsAll">
+ <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">
<button class="btn btn-sm btn-outline-primary" id="dropdownSelect" ngbDropdownToggle>
<svg class="toolbaricon" fill="currentColor">
<div class="d-none d-sm-inline"> <ng-container i18n>Actions</ng-container></div>
</button>
<div ngbDropdownMenu aria-labelledby="dropdownSelect" class="shadow">
- <button ngbDropdownItem [disabled]="awaitingDownload" (click)="downloadSelected()" i18n>
- Download
- <div *ngIf="awaitingDownload" class="spinner-border spinner-border-sm" role="status">
- <span class="visually-hidden">Preparing download...</span>
- <button ngbDropdownItem (click)="redoOcrSelected()" i18n>Redo OCR</button>
++ <button ngbDropdownItem (click)="redoOcrSelected()" [disabled]="!userCanEditAll" i18n>Redo OCR</button>
+ </div>
+ </div>
+ </div>
+
+ <div class="btn-group btn-group-sm me-2">
+ <button class="btn btn-sm btn-outline-primary" [disabled]="awaitingDownload" (click)="downloadSelected()">
+ <svg *ngIf="!awaitingDownload" class="toolbaricon" fill="currentColor">
+ <use xlink:href="assets/bootstrap-icons.svg#arrow-down" />
+ </svg>
+ <div *ngIf="awaitingDownload" class="spinner-border spinner-border-sm" role="status">
+ <span class="visually-hidden">Preparing download...</span>
+ </div>
+ <div class="d-none d-sm-inline"> <ng-container i18n>Download</ng-container></div>
+ </button>
+ <div ngbDropdown class="me-2 d-flex btn-group" role="group">
+ <button type="button" class="btn btn-sm btn-outline-primary dropdown-toggle-split rounded-end" ngbDropdownToggle></button>
+ <div ngbDropdownMenu aria-labelledby="dropdownSelect" class="shadow">
+ <form [formGroup]="downloadForm" class="px-3 py-1">
+ <p class="mb-1" i18n>Include:</p>
+ <div class="form-group ps-3 mb-2">
+ <div class="form-check">
+ <input type="checkbox" class="form-check-input" id="downloadFileType_archive" formControlName="downloadFileTypeArchive" />
+ <label class="form-check-label" for="downloadFileType_archive" i18n>
+ Archived files
+ </label>
+ </div>
+ <div class="form-check">
+ <input type="checkbox" class="form-check-input" id="downloadFileType_originals" formControlName="downloadFileTypeOriginals" />
+ <label class="form-check-label" for="downloadFileType_originals" i18n>
+ Original files
+ </label>
+ </div>
</div>
- </button>
- <button ngbDropdownItem [disabled]="awaitingDownload" (click)="downloadSelected('originals')" i18n>
- Download originals
- <div *ngIf="awaitingDownload" class="spinner-border spinner-border-sm" role="status">
- <span class="visually-hidden">Preparing download...</span>
+ <div class="form-check">
+ <input type="checkbox" class="form-check-input" id="downloadUseFormatting" formControlName="downloadUseFormatting" />
+ <label class="form-check-label" for="downloadUseFormatting" i18n>
+ Use formatted filename
+ </label>
</div>
- </button>
- <button ngbDropdownItem (click)="redoOcrSelected()" [disabled]="!userCanEditAll" i18n>Redo OCR</button>
+ </form>
</div>
</div>
+ </div>
- <button type="button" class="btn btn-sm btn-outline-danger" (click)="applyDelete()" *ifPermissions="{ action: PermissionAction.Delete, type: PermissionType.Document }" [disabled]="!userOwnsAll">
- <svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
- <use xlink:href="assets/bootstrap-icons.svg#trash" />
- </svg> <ng-container i18n>Delete</ng-container>
- </button>
+ <div class="btn-group btn-group-sm me-2">
- <button type="button" class="btn btn-sm btn-outline-danger" (click)="applyDelete()">
++ <button type="button" class="btn btn-sm btn-outline-danger" (click)="applyDelete()" *ifPermissions="{ action: PermissionAction.Delete, type: PermissionType.Document }" [disabled]="!userOwnsAll">
+ <svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
+ <use xlink:href="assets/bootstrap-icons.svg#trash" />
+ </svg> <ng-container i18n>Delete</ng-container>
+ </button>
+ </div>
</div>
</div>
import { StoragePathService } from 'src/app/services/rest/storage-path.service'
import { PaperlessStoragePath } from 'src/app/data/paperless-storage-path'
import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
+import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component'
+import { PermissionsDialogComponent } from '../../common/permissions-dialog/permissions-dialog.component'
+import { PermissionsService } from 'src/app/services/permissions.service'
+ import { FormControl, FormGroup } from '@angular/forms'
+ import { first, Subject, takeUntil } from 'rxjs'
@Component({
selector: 'app-bulk-editor',
modal.componentInstance.message = $localize`This operation cannot be undone.`
modal.componentInstance.btnClass = 'btn-danger'
modal.componentInstance.btnCaption = $localize`Proceed`
- modal.componentInstance.confirmClicked.subscribe(() => {
- modal.componentInstance.buttonsEnabled = false
- this.executeBulkOperation(modal, 'redo_ocr', {})
- })
+ modal.componentInstance.confirmClicked
+ .pipe(takeUntil(this.unsubscribeNotifier))
+ .subscribe(() => {
+ modal.componentInstance.buttonsEnabled = false
+ 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,
+ })
+ })
+ }
}
],
)
+ 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):
+
+ ENDPOINT = "/api/documents/bulk_download/"
+
def setUp(self):
super().setUp()