Docs only
[skip ci]
This commit is contained in:
parent
fc1423057d
commit
872fc206b4
@ -3,19 +3,19 @@ title="Trash"
|
|||||||
i18n-title
|
i18n-title
|
||||||
info="Manage trashed items."
|
info="Manage trashed items."
|
||||||
>
|
>
|
||||||
<button class="btn btn-sm btn-outline-secondary" (click)="clearSelection()" [hidden]="selectedObjects.size === 0">
|
<button class="btn btn-sm btn-outline-secondary" (click)="clearSelection()" [hidden]="selectedDocuments.size === 0">
|
||||||
<i-bs name="x"></i-bs> <ng-container i18n>Clear selection</ng-container>
|
<i-bs name="x"></i-bs> <ng-container i18n>Clear selection</ng-container>
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="btn btn-sm btn-outline-danger" (click)="emptyTrash(selectedObjects)" [disabled]="selectedObjects.size === 0">
|
<button type="button" class="btn btn-sm btn-outline-danger" (click)="emptyTrash(selectedDocuments)" [disabled]="selectedDocuments.size === 0">
|
||||||
<i-bs name="trash"></i-bs> <ng-container i18n>Delete objects</ng-container>
|
<i-bs name="trash"></i-bs> <ng-container i18n>Delete selected</ng-container>
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="btn btn-sm btn-outline-danger" (click)="emptyTrash()" [disabled]="trashedObjects.length === 0">
|
<button type="button" class="btn btn-sm btn-outline-danger" (click)="emptyTrash()" [disabled]="documentsInTrash.length === 0">
|
||||||
<i-bs name="trash"></i-bs> <ng-container i18n>Empty trash</ng-container>
|
<i-bs name="trash"></i-bs> <ng-container i18n>Empty trash</ng-container>
|
||||||
</button>
|
</button>
|
||||||
</pngx-page-header>
|
</pngx-page-header>
|
||||||
|
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<ngb-pagination class="col-auto" [pageSize]="25" [collectionSize]="trashedObjects.length" [(page)]="page" [maxSize]="5" (pageChange)="reload()" size="sm" aria-label="Pagination"></ngb-pagination>
|
<ngb-pagination class="col-auto" [pageSize]="25" [collectionSize]="documentsInTrash.length" [(page)]="page" [maxSize]="5" (pageChange)="reload()" size="sm" aria-label="Pagination"></ngb-pagination>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card border table-responsive mb-3">
|
<div class="card border table-responsive mb-3">
|
||||||
@ -24,7 +24,7 @@ info="Manage trashed items."
|
|||||||
<tr>
|
<tr>
|
||||||
<th scope="col">
|
<th scope="col">
|
||||||
<div class="form-check m-0 ms-2 me-n2">
|
<div class="form-check m-0 ms-2 me-n2">
|
||||||
<input type="checkbox" class="form-check-input" id="all-objects" [(ngModel)]="togggleAll" [disabled]="trashedObjects.length === 0" (click)="toggleAll($event); $event.stopPropagation();">
|
<input type="checkbox" class="form-check-input" id="all-objects" [(ngModel)]="togggleAll" [disabled]="documentsInTrash.length === 0" (click)="toggleAll($event); $event.stopPropagation();">
|
||||||
<label class="form-check-label" for="all-objects"></label>
|
<label class="form-check-label" for="all-objects"></label>
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
@ -42,16 +42,16 @@ info="Manage trashed items."
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
@for (object of trashedObjects; track object.id) {
|
@for (document of documentsInTrash; track document.id) {
|
||||||
<tr (click)="toggleSelected(object); $event.stopPropagation();">
|
<tr (click)="toggleSelected(document); $event.stopPropagation();">
|
||||||
<td>
|
<td>
|
||||||
<div class="form-check m-0 ms-2 me-n2">
|
<div class="form-check m-0 ms-2 me-n2">
|
||||||
<input type="checkbox" class="form-check-input" id="{{object.id}}" [checked]="selectedObjects.has(object.id)" (click)="toggleSelected(object); $event.stopPropagation();">
|
<input type="checkbox" class="form-check-input" id="{{document.id}}" [checked]="selectedDocuments.has(document.id)" (click)="toggleSelected(document); $event.stopPropagation();">
|
||||||
<label class="form-check-label" for="{{object.id}}"></label>
|
<label class="form-check-label" for="{{document.id}}"></label>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td scope="row">{{ object['name'] ?? object['title'] }}</td>
|
<td scope="row">{{ document.title }}</td>
|
||||||
<td scope="row">{{ object['deleted_at'] | customDate }}</td>
|
<td scope="row">{{ document.deleted_at | customDate }}</td>
|
||||||
<td scope="row">
|
<td scope="row">
|
||||||
<div class="btn-group d-block d-sm-none">
|
<div class="btn-group d-block d-sm-none">
|
||||||
<div ngbDropdown container="body" class="d-inline-block">
|
<div ngbDropdown container="body" class="d-inline-block">
|
||||||
@ -59,16 +59,16 @@ info="Manage trashed items."
|
|||||||
<i-bs name="three-dots-vertical"></i-bs>
|
<i-bs name="three-dots-vertical"></i-bs>
|
||||||
</button>
|
</button>
|
||||||
<div ngbDropdownMenu aria-labelledby="actionsMenuMobile">
|
<div ngbDropdownMenu aria-labelledby="actionsMenuMobile">
|
||||||
<button (click)="restoreObject(object)" ngbDropdownItem i18n>Restore</button>
|
<button (click)="restore(document)" ngbDropdownItem i18n>Restore</button>
|
||||||
<button (click)="deleteObject(object)" ngbDropdownItem i18n>Delete</button>
|
<button (click)="delete(document)" ngbDropdownItem i18n>Delete</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="btn-group d-none d-sm-block">
|
<div class="btn-group d-none d-sm-block">
|
||||||
<button class="btn btn-sm btn-outline-secondary" (click)="restoreObject(object); $event.stopPropagation();">
|
<button class="btn btn-sm btn-outline-secondary" (click)="restore(document); $event.stopPropagation();">
|
||||||
<i-bs width="1em" height="1em" name="restore"></i-bs> <ng-container i18n>Restore</ng-container>
|
<i-bs width="1em" height="1em" name="restore"></i-bs> <ng-container i18n>Restore</ng-container>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-sm btn-outline-danger" (click)="deleteObject(object); $event.stopPropagation();">
|
<button class="btn btn-sm btn-outline-danger" (click)="delete(document); $event.stopPropagation();">
|
||||||
<i-bs width="1em" height="1em" name="trash"></i-bs> <ng-container i18n>Delete</ng-container>
|
<i-bs width="1em" height="1em" name="trash"></i-bs> <ng-container i18n>Delete</ng-container>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -82,13 +82,13 @@ info="Manage trashed items."
|
|||||||
@if (!isLoading) {
|
@if (!isLoading) {
|
||||||
<div class="d-flex mb-2">
|
<div class="d-flex mb-2">
|
||||||
<div>
|
<div>
|
||||||
<ng-container i18n>{trashedObjects.length, plural, =1 {One object in trash} other {{{trashedObjects.length || 0}} total objects in trash}}</ng-container>
|
<ng-container i18n>{documentsInTrash.length, plural, =1 {One object in trash} other {{{documentsInTrash.length || 0}} total documents in trash}}</ng-container>
|
||||||
@if (selectedObjects.size > 0) {
|
@if (selectedDocuments.size > 0) {
|
||||||
({{selectedObjects.size}} selected)
|
({{selectedDocuments.size}} selected)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@if (trashedObjects.length > 20) {
|
@if (documentsInTrash.length > 20) {
|
||||||
<ngb-pagination class="ms-auto" [pageSize]="25" [collectionSize]="trashedObjects.length" [(page)]="page" [maxSize]="5" (pageChange)="reload()" size="sm" aria-label="Pagination"></ngb-pagination>
|
<ngb-pagination class="ms-auto" [pageSize]="25" [collectionSize]="documentsInTrash.length" [(page)]="page" [maxSize]="5" (pageChange)="reload()" size="sm" aria-label="Pagination"></ngb-pagination>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
@ -1,57 +1,91 @@
|
|||||||
import { HttpClient } from '@angular/common/http'
|
import { Component, OnDestroy } from '@angular/core'
|
||||||
import { Component } from '@angular/core'
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { ObjectWithId } from 'src/app/data/object-with-id'
|
import { Document } from 'src/app/data/document'
|
||||||
import { ToastService } from 'src/app/services/toast.service'
|
import { ToastService } from 'src/app/services/toast.service'
|
||||||
import { TrashService } from 'src/app/services/trash.service'
|
import { TrashService } from 'src/app/services/trash.service'
|
||||||
import { environment } from 'src/environments/environment'
|
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
|
||||||
|
import { Subject, takeUntil } from 'rxjs'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'pngx-trash',
|
selector: 'pngx-trash',
|
||||||
templateUrl: './trash.component.html',
|
templateUrl: './trash.component.html',
|
||||||
styleUrl: './trash.component.scss',
|
styleUrl: './trash.component.scss',
|
||||||
})
|
})
|
||||||
export class TrashComponent {
|
export class TrashComponent implements OnDestroy {
|
||||||
public trashedObjects: ObjectWithId[] = []
|
public documentsInTrash: Document[] = []
|
||||||
public selectedObjects: Set<number> = new Set()
|
public selectedDocuments: Set<number> = new Set()
|
||||||
public togggleAll: boolean = false
|
public togggleAll: boolean = false
|
||||||
public page: number = 1
|
public page: number = 1
|
||||||
public isLoading: boolean = false
|
public isLoading: boolean = false
|
||||||
|
unsubscribeNotifier: Subject<void> = new Subject()
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private trashService: TrashService,
|
private trashService: TrashService,
|
||||||
private toastService: ToastService
|
private toastService: ToastService,
|
||||||
|
private modalService: NgbModal
|
||||||
) {
|
) {
|
||||||
this.reload()
|
this.reload()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.unsubscribeNotifier.next()
|
||||||
|
this.unsubscribeNotifier.complete()
|
||||||
|
}
|
||||||
|
|
||||||
reload() {
|
reload() {
|
||||||
this.isLoading = true
|
this.isLoading = true
|
||||||
this.trashService.getTrash().subscribe((trash) => {
|
this.trashService.getTrash().subscribe((documentsInTrash) => {
|
||||||
this.trashedObjects = trash
|
this.documentsInTrash = documentsInTrash
|
||||||
this.isLoading = false
|
this.isLoading = false
|
||||||
console.log('Trash:', trash)
|
console.log('Trash:', documentsInTrash)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteObject(object: ObjectWithId) {
|
delete(document: Document) {
|
||||||
this.trashService.emptyTrash([object.id]).subscribe(() => {
|
let modal = this.modalService.open(ConfirmDialogComponent, {
|
||||||
this.toastService.showInfo($localize`Object deleted`)
|
backdrop: 'static',
|
||||||
this.reload()
|
|
||||||
})
|
})
|
||||||
}
|
modal.componentInstance.title = $localize`Confirm delete`
|
||||||
|
modal.componentInstance.messageBold = $localize`This operation will permanently delete this document.`
|
||||||
emptyTrash(objects: Set<number> = null) {
|
modal.componentInstance.message = $localize`This operation cannot be undone.`
|
||||||
console.log('Emptying trash')
|
modal.componentInstance.btnClass = 'btn-danger'
|
||||||
this.trashService
|
modal.componentInstance.btnCaption = $localize`Delete`
|
||||||
.emptyTrash(objects ? Array.from(objects) : [])
|
modal.componentInstance.confirmClicked
|
||||||
|
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||||
.subscribe(() => {
|
.subscribe(() => {
|
||||||
this.toastService.showInfo($localize`Object(s) deleted`)
|
modal.componentInstance.buttonsEnabled = false
|
||||||
|
this.trashService.emptyTrash([document.id]).subscribe(() => {
|
||||||
|
this.toastService.showInfo($localize`Document deleted`)
|
||||||
this.reload()
|
this.reload()
|
||||||
})
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
restoreObject(object: ObjectWithId) {
|
emptyTrash(documents: Set<number> = null) {
|
||||||
this.trashService.restoreObjects([object.id]).subscribe(() => {
|
let modal = this.modalService.open(ConfirmDialogComponent, {
|
||||||
|
backdrop: 'static',
|
||||||
|
})
|
||||||
|
modal.componentInstance.title = $localize`Confirm delete`
|
||||||
|
modal.componentInstance.messageBold = $localize`This operation will permanently delete ${
|
||||||
|
documents?.size ?? $localize`all`
|
||||||
|
} documents.`
|
||||||
|
modal.componentInstance.message = $localize`This operation cannot be undone.`
|
||||||
|
modal.componentInstance.btnClass = 'btn-danger'
|
||||||
|
modal.componentInstance.btnCaption = $localize`Delete`
|
||||||
|
modal.componentInstance.confirmClicked
|
||||||
|
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||||
|
.subscribe(() => {
|
||||||
|
this.trashService
|
||||||
|
.emptyTrash(documents ? Array.from(documents) : [])
|
||||||
|
.subscribe(() => {
|
||||||
|
this.toastService.showInfo($localize`Document(s) deleted`)
|
||||||
|
this.reload()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
restore(document: Document) {
|
||||||
|
this.trashService.restoreDocuments([document.id]).subscribe(() => {
|
||||||
this.toastService.showInfo($localize`Object restored`)
|
this.toastService.showInfo($localize`Object restored`)
|
||||||
this.reload()
|
this.reload()
|
||||||
})
|
})
|
||||||
@ -59,7 +93,7 @@ export class TrashComponent {
|
|||||||
|
|
||||||
restoreAll(objects: Set<number> = null) {
|
restoreAll(objects: Set<number> = null) {
|
||||||
this.trashService
|
this.trashService
|
||||||
.restoreObjects(objects ? Array.from(this.selectedObjects) : [])
|
.restoreDocuments(objects ? Array.from(this.selectedDocuments) : [])
|
||||||
.subscribe(() => {
|
.subscribe(() => {
|
||||||
this.toastService.showInfo($localize`Object(s) restored`)
|
this.toastService.showInfo($localize`Object(s) restored`)
|
||||||
this.reload()
|
this.reload()
|
||||||
@ -68,20 +102,20 @@ export class TrashComponent {
|
|||||||
|
|
||||||
toggleAll(event: PointerEvent) {
|
toggleAll(event: PointerEvent) {
|
||||||
if ((event.target as HTMLInputElement).checked) {
|
if ((event.target as HTMLInputElement).checked) {
|
||||||
this.selectedObjects = new Set(this.trashedObjects.map((t) => t.id))
|
this.selectedDocuments = new Set(this.documentsInTrash.map((t) => t.id))
|
||||||
} else {
|
} else {
|
||||||
this.clearSelection()
|
this.clearSelection()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleSelected(object: ObjectWithId) {
|
toggleSelected(object: Document) {
|
||||||
this.selectedObjects.has(object.id)
|
this.selectedDocuments.has(object.id)
|
||||||
? this.selectedObjects.delete(object.id)
|
? this.selectedDocuments.delete(object.id)
|
||||||
: this.selectedObjects.add(object.id)
|
: this.selectedDocuments.add(object.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
clearSelection() {
|
clearSelection() {
|
||||||
this.togggleAll = false
|
this.togggleAll = false
|
||||||
this.selectedObjects.clear()
|
this.selectedDocuments.clear()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -86,14 +86,4 @@ describe('ConfirmDialogComponent', () => {
|
|||||||
expect(closeModalSpy).toHaveBeenCalled()
|
expect(closeModalSpy).toHaveBeenCalled()
|
||||||
expect(confirmSubjectResult).toBeFalsy()
|
expect(confirmSubjectResult).toBeFalsy()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should support delay confirm', fakeAsync(() => {
|
|
||||||
component.confirmButtonEnabled = false
|
|
||||||
component.delayConfirm(1)
|
|
||||||
expect(component.confirmButtonEnabled).toBeFalsy()
|
|
||||||
tick(1500)
|
|
||||||
fixture.detectChanges()
|
|
||||||
expect(component.confirmButtonEnabled).toBeTruthy()
|
|
||||||
discardPeriodicTasks()
|
|
||||||
}))
|
|
||||||
})
|
})
|
||||||
|
@ -54,26 +54,6 @@ export class ConfirmDialogComponent {
|
|||||||
confirmSubject: Subject<boolean>
|
confirmSubject: Subject<boolean>
|
||||||
alternativeSubject: Subject<boolean>
|
alternativeSubject: Subject<boolean>
|
||||||
|
|
||||||
delayConfirm(seconds: number) {
|
|
||||||
const refreshInterval = 0.15 // s
|
|
||||||
|
|
||||||
this.secondsTotal = seconds
|
|
||||||
this.seconds = seconds
|
|
||||||
|
|
||||||
interval(refreshInterval * 1000)
|
|
||||||
.pipe(
|
|
||||||
take(this.secondsTotal / refreshInterval + 2) // need 2 more for animation to complete after 0
|
|
||||||
)
|
|
||||||
.subscribe((count) => {
|
|
||||||
this.seconds = Math.max(
|
|
||||||
0,
|
|
||||||
this.secondsTotal - refreshInterval * (count + 1)
|
|
||||||
)
|
|
||||||
this.confirmButtonEnabled =
|
|
||||||
this.secondsTotal - refreshInterval * count < 0
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
cancel() {
|
cancel() {
|
||||||
this.confirmSubject?.next(false)
|
this.confirmSubject?.next(false)
|
||||||
this.confirmSubject?.complete()
|
this.confirmSubject?.complete()
|
||||||
|
@ -773,11 +773,11 @@ export class DocumentDetailComponent
|
|||||||
let modal = this.modalService.open(ConfirmDialogComponent, {
|
let modal = this.modalService.open(ConfirmDialogComponent, {
|
||||||
backdrop: 'static',
|
backdrop: 'static',
|
||||||
})
|
})
|
||||||
modal.componentInstance.title = $localize`Confirm delete`
|
modal.componentInstance.title = $localize`Confirm`
|
||||||
modal.componentInstance.messageBold = $localize`Do you really want to delete document "${this.document.title}"?`
|
modal.componentInstance.messageBold = $localize`Do you really want to move the document "${this.document.title}" to the trash?`
|
||||||
modal.componentInstance.message = $localize`The files for this document will be deleted permanently. This operation cannot be undone.`
|
modal.componentInstance.message = $localize`Documents can be restored prior to permanent deletion.`
|
||||||
modal.componentInstance.btnClass = 'btn-danger'
|
modal.componentInstance.btnClass = 'btn-danger'
|
||||||
modal.componentInstance.btnCaption = $localize`Delete document`
|
modal.componentInstance.btnCaption = $localize`Move to trash`
|
||||||
this.subscribeModalDelete(modal) // so can be re-subscribed if error
|
this.subscribeModalDelete(modal) // so can be re-subscribed if error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -708,8 +708,9 @@ export class BulkEditorComponent
|
|||||||
let modal = this.modalService.open(ConfirmDialogComponent, {
|
let modal = this.modalService.open(ConfirmDialogComponent, {
|
||||||
backdrop: 'static',
|
backdrop: 'static',
|
||||||
})
|
})
|
||||||
modal.componentInstance.title = $localize`Delete confirm`
|
modal.componentInstance.title = $localize`Confirm`
|
||||||
modal.componentInstance.messageBold = $localize`Move ${this.list.selected.size} selected document(s) to the trash?`
|
modal.componentInstance.messageBold = $localize`Move ${this.list.selected.size} selected document(s) to the trash?`
|
||||||
|
modal.componentInstance.message = $localize`Documents can be restored prior to permanent deletion.`
|
||||||
modal.componentInstance.btnClass = 'btn-danger'
|
modal.componentInstance.btnClass = 'btn-danger'
|
||||||
modal.componentInstance.btnCaption = $localize`Move to trash`
|
modal.componentInstance.btnCaption = $localize`Move to trash`
|
||||||
modal.componentInstance.confirmClicked
|
modal.componentInstance.confirmClicked
|
||||||
|
@ -2,7 +2,7 @@ import { HttpClient } from '@angular/common/http'
|
|||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { Observable } from 'rxjs'
|
import { Observable } from 'rxjs'
|
||||||
import { environment } from 'src/environments/environment'
|
import { environment } from 'src/environments/environment'
|
||||||
import { ObjectWithId } from '../data/object-with-id'
|
import { Document } from '../data/document'
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
@ -10,18 +10,18 @@ import { ObjectWithId } from '../data/object-with-id'
|
|||||||
export class TrashService {
|
export class TrashService {
|
||||||
constructor(private http: HttpClient) {}
|
constructor(private http: HttpClient) {}
|
||||||
|
|
||||||
public getTrash(): Observable<ObjectWithId[]> {
|
public getTrash(): Observable<Document[]> {
|
||||||
return this.http.get<ObjectWithId[]>(`${environment.apiBaseUrl}trash/`)
|
return this.http.get<Document[]>(`${environment.apiBaseUrl}trash/`)
|
||||||
}
|
}
|
||||||
|
|
||||||
public emptyTrash(documents: number[] = []) {
|
public emptyTrash(documents: number[] = []): Observable<any> {
|
||||||
return this.http.post(`${environment.apiBaseUrl}trash/`, {
|
return this.http.post(`${environment.apiBaseUrl}trash/`, {
|
||||||
action: 'empty',
|
action: 'empty',
|
||||||
documents,
|
documents,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public restoreObjects(documents: number[]): Observable<any> {
|
public restoreDocuments(documents: number[]): Observable<any> {
|
||||||
return this.http.post(`${environment.apiBaseUrl}trash/`, {
|
return this.http.post(`${environment.apiBaseUrl}trash/`, {
|
||||||
action: 'restore',
|
action: 'restore',
|
||||||
documents,
|
documents,
|
||||||
|
@ -1,154 +0,0 @@
|
|||||||
# Generated by Django 4.2.11 on 2024-04-22 20:44
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("documents", "1046_workflowaction_remove_all_correspondents_and_more"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="correspondent",
|
|
||||||
name="deleted_at",
|
|
||||||
field=models.DateTimeField(blank=True, null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="correspondent",
|
|
||||||
name="restored_at",
|
|
||||||
field=models.DateTimeField(blank=True, null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="document",
|
|
||||||
name="deleted_at",
|
|
||||||
field=models.DateTimeField(blank=True, null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="document",
|
|
||||||
name="restored_at",
|
|
||||||
field=models.DateTimeField(blank=True, null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="documenttype",
|
|
||||||
name="deleted_at",
|
|
||||||
field=models.DateTimeField(blank=True, null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="documenttype",
|
|
||||||
name="restored_at",
|
|
||||||
field=models.DateTimeField(blank=True, null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="savedview",
|
|
||||||
name="deleted_at",
|
|
||||||
field=models.DateTimeField(blank=True, null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="savedview",
|
|
||||||
name="restored_at",
|
|
||||||
field=models.DateTimeField(blank=True, null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="storagepath",
|
|
||||||
name="deleted_at",
|
|
||||||
field=models.DateTimeField(blank=True, null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="storagepath",
|
|
||||||
name="restored_at",
|
|
||||||
field=models.DateTimeField(blank=True, null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="tag",
|
|
||||||
name="deleted_at",
|
|
||||||
field=models.DateTimeField(blank=True, null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="tag",
|
|
||||||
name="restored_at",
|
|
||||||
field=models.DateTimeField(blank=True, null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="workflow",
|
|
||||||
name="deleted_at",
|
|
||||||
field=models.DateTimeField(blank=True, null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="workflow",
|
|
||||||
name="restored_at",
|
|
||||||
field=models.DateTimeField(blank=True, null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="note",
|
|
||||||
name="deleted_at",
|
|
||||||
field=models.DateTimeField(blank=True, null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="note",
|
|
||||||
name="restored_at",
|
|
||||||
field=models.DateTimeField(blank=True, null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="customfield",
|
|
||||||
name="deleted_at",
|
|
||||||
field=models.DateTimeField(blank=True, null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="customfield",
|
|
||||||
name="restored_at",
|
|
||||||
field=models.DateTimeField(blank=True, null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="customfieldinstance",
|
|
||||||
name="deleted_at",
|
|
||||||
field=models.DateTimeField(blank=True, null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="customfieldinstance",
|
|
||||||
name="restored_at",
|
|
||||||
field=models.DateTimeField(blank=True, null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="savedviewfilterrule",
|
|
||||||
name="deleted_at",
|
|
||||||
field=models.DateTimeField(blank=True, null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="savedviewfilterrule",
|
|
||||||
name="restored_at",
|
|
||||||
field=models.DateTimeField(blank=True, null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="sharelink",
|
|
||||||
name="deleted_at",
|
|
||||||
field=models.DateTimeField(blank=True, null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="sharelink",
|
|
||||||
name="restored_at",
|
|
||||||
field=models.DateTimeField(blank=True, null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="workflowaction",
|
|
||||||
name="deleted_at",
|
|
||||||
field=models.DateTimeField(blank=True, null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="workflowaction",
|
|
||||||
name="restored_at",
|
|
||||||
field=models.DateTimeField(blank=True, null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="workflowtrigger",
|
|
||||||
name="deleted_at",
|
|
||||||
field=models.DateTimeField(blank=True, null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="workflowtrigger",
|
|
||||||
name="restored_at",
|
|
||||||
field=models.DateTimeField(blank=True, null=True),
|
|
||||||
),
|
|
||||||
]
|
|
@ -0,0 +1,24 @@
|
|||||||
|
# Generated by Django 4.2.11 on 2024-04-23 07:56
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("documents", "1048_alter_savedviewfilterrule_rule_type"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="document",
|
||||||
|
name="deleted_at",
|
||||||
|
field=models.DateTimeField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="document",
|
||||||
|
name="restored_at",
|
||||||
|
field=models.DateTimeField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
]
|
@ -29,7 +29,7 @@ from documents.data_models import DocumentSource
|
|||||||
from documents.parsers import get_default_file_extension
|
from documents.parsers import get_default_file_extension
|
||||||
|
|
||||||
|
|
||||||
class ModelWithOwner(SoftDeleteModel):
|
class ModelWithOwner(models.Model):
|
||||||
owner = models.ForeignKey(
|
owner = models.ForeignKey(
|
||||||
User,
|
User,
|
||||||
blank=True,
|
blank=True,
|
||||||
@ -132,7 +132,7 @@ class StoragePath(MatchingModel):
|
|||||||
verbose_name_plural = _("storage paths")
|
verbose_name_plural = _("storage paths")
|
||||||
|
|
||||||
|
|
||||||
class Document(ModelWithOwner):
|
class Document(SoftDeleteModel, ModelWithOwner):
|
||||||
STORAGE_TYPE_UNENCRYPTED = "unencrypted"
|
STORAGE_TYPE_UNENCRYPTED = "unencrypted"
|
||||||
STORAGE_TYPE_GPG = "gpg"
|
STORAGE_TYPE_GPG = "gpg"
|
||||||
STORAGE_TYPES = (
|
STORAGE_TYPES = (
|
||||||
@ -462,7 +462,7 @@ class SavedView(ModelWithOwner):
|
|||||||
return f"SavedView {self.name}"
|
return f"SavedView {self.name}"
|
||||||
|
|
||||||
|
|
||||||
class SavedViewFilterRule(SoftDeleteModel):
|
class SavedViewFilterRule(models.Model):
|
||||||
RULE_TYPES = [
|
RULE_TYPES = [
|
||||||
(0, _("title contains")),
|
(0, _("title contains")),
|
||||||
(1, _("content contains")),
|
(1, _("content contains")),
|
||||||
@ -694,7 +694,7 @@ class PaperlessTask(models.Model):
|
|||||||
return f"Task {self.task_id}"
|
return f"Task {self.task_id}"
|
||||||
|
|
||||||
|
|
||||||
class Note(SoftDeleteModel):
|
class Note(models.Model):
|
||||||
note = models.TextField(
|
note = models.TextField(
|
||||||
_("content"),
|
_("content"),
|
||||||
blank=True,
|
blank=True,
|
||||||
@ -734,7 +734,7 @@ class Note(SoftDeleteModel):
|
|||||||
return self.note
|
return self.note
|
||||||
|
|
||||||
|
|
||||||
class ShareLink(SoftDeleteModel):
|
class ShareLink(models.Model):
|
||||||
class FileVersion(models.TextChoices):
|
class FileVersion(models.TextChoices):
|
||||||
ARCHIVE = ("archive", _("Archive"))
|
ARCHIVE = ("archive", _("Archive"))
|
||||||
ORIGINAL = ("original", _("Original"))
|
ORIGINAL = ("original", _("Original"))
|
||||||
@ -794,7 +794,7 @@ class ShareLink(SoftDeleteModel):
|
|||||||
return f"Share Link for {self.document.title}"
|
return f"Share Link for {self.document.title}"
|
||||||
|
|
||||||
|
|
||||||
class CustomField(SoftDeleteModel):
|
class CustomField(models.Model):
|
||||||
"""
|
"""
|
||||||
Defines the name and type of a custom field
|
Defines the name and type of a custom field
|
||||||
"""
|
"""
|
||||||
@ -840,7 +840,7 @@ class CustomField(SoftDeleteModel):
|
|||||||
return f"{self.name} : {self.data_type}"
|
return f"{self.name} : {self.data_type}"
|
||||||
|
|
||||||
|
|
||||||
class CustomFieldInstance(SoftDeleteModel):
|
class CustomFieldInstance(models.Model):
|
||||||
"""
|
"""
|
||||||
A single instance of a field, attached to a CustomField for the name and type
|
A single instance of a field, attached to a CustomField for the name and type
|
||||||
and attached to a single Document to be metadata for it
|
and attached to a single Document to be metadata for it
|
||||||
@ -941,7 +941,7 @@ if settings.AUDIT_LOG_ENABLED:
|
|||||||
auditlog.register(CustomFieldInstance)
|
auditlog.register(CustomFieldInstance)
|
||||||
|
|
||||||
|
|
||||||
class WorkflowTrigger(SoftDeleteModel):
|
class WorkflowTrigger(models.Model):
|
||||||
class WorkflowTriggerMatching(models.IntegerChoices):
|
class WorkflowTriggerMatching(models.IntegerChoices):
|
||||||
# No auto matching
|
# No auto matching
|
||||||
NONE = MatchingModel.MATCH_NONE, _("None")
|
NONE = MatchingModel.MATCH_NONE, _("None")
|
||||||
@ -1045,7 +1045,7 @@ class WorkflowTrigger(SoftDeleteModel):
|
|||||||
return f"WorkflowTrigger {self.pk}"
|
return f"WorkflowTrigger {self.pk}"
|
||||||
|
|
||||||
|
|
||||||
class WorkflowAction(SoftDeleteModel):
|
class WorkflowAction(models.Model):
|
||||||
class WorkflowActionType(models.IntegerChoices):
|
class WorkflowActionType(models.IntegerChoices):
|
||||||
ASSIGNMENT = (
|
ASSIGNMENT = (
|
||||||
1,
|
1,
|
||||||
@ -1264,7 +1264,7 @@ class WorkflowAction(SoftDeleteModel):
|
|||||||
return f"WorkflowAction {self.pk}"
|
return f"WorkflowAction {self.pk}"
|
||||||
|
|
||||||
|
|
||||||
class Workflow(SoftDeleteModel):
|
class Workflow(models.Model):
|
||||||
name = models.CharField(_("name"), max_length=256, unique=True)
|
name = models.CharField(_("name"), max_length=256, unique=True)
|
||||||
|
|
||||||
order = models.IntegerField(_("order"), default=0)
|
order = models.IntegerField(_("order"), default=0)
|
||||||
|
@ -308,8 +308,6 @@ def cleanup_document_deletion(sender, instance, force=False, **kwargs):
|
|||||||
now = timezone.localtime(timezone.now())
|
now = timezone.localtime(timezone.now())
|
||||||
if now - instance.deleted_at < timedelta(days=settings.EMPTY_TRASH_DELAY):
|
if now - instance.deleted_at < timedelta(days=settings.EMPTY_TRASH_DELAY):
|
||||||
return
|
return
|
||||||
# print(instance.pk, force, kwargs)
|
|
||||||
return
|
|
||||||
with FileLock(settings.MEDIA_LOCK):
|
with FileLock(settings.MEDIA_LOCK):
|
||||||
if settings.TRASH_DIR:
|
if settings.TRASH_DIR:
|
||||||
# Find a non-conflicting filename in case a document with the same
|
# Find a non-conflicting filename in case a document with the same
|
||||||
|
@ -307,27 +307,10 @@ def empty_trash(doc_ids=None):
|
|||||||
if doc_ids is not None
|
if doc_ids is not None
|
||||||
else Document.deleted_objects.filter(deleted_at__gt=cutoff)
|
else Document.deleted_objects.filter(deleted_at__gt=cutoff)
|
||||||
)
|
)
|
||||||
# print(documents, doc_ids)
|
|
||||||
for doc in documents:
|
for doc in documents:
|
||||||
# with disable_signal(
|
|
||||||
# post_delete,
|
|
||||||
# receiver=cleanup_document_deletion,
|
|
||||||
# sender=Document,
|
|
||||||
# ):
|
|
||||||
doc.delete()
|
doc.delete()
|
||||||
post_delete.send(
|
post_delete.send(
|
||||||
sender=Document,
|
sender=Document,
|
||||||
instance=doc,
|
instance=doc,
|
||||||
force=True,
|
force=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
# messages.log_messages()
|
|
||||||
|
|
||||||
# if messages.has_error:
|
|
||||||
# raise SanityCheckFailedException("Sanity check failed with errors. See log.")
|
|
||||||
# elif messages.has_warning:
|
|
||||||
# return "Sanity check exited with warnings. See log."
|
|
||||||
# elif len(messages) > 0:
|
|
||||||
# return "Sanity check exited with infos. See log."
|
|
||||||
# else:
|
|
||||||
# return "No issues detected."
|
|
||||||
|
@ -2077,9 +2077,6 @@ class TrashView(PassUserMixin):
|
|||||||
serializer = self.get_serializer(data=request.data)
|
serializer = self.get_serializer(data=request.data)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
|
|
||||||
# user = self.request.user
|
|
||||||
# method = serializer.validated_data.get("method")
|
|
||||||
# parameters = serializer.validated_data.get("parameters")
|
|
||||||
doc_ids = serializer.validated_data.get("documents")
|
doc_ids = serializer.validated_data.get("documents")
|
||||||
action = serializer.validated_data.get("action")
|
action = serializer.validated_data.get("action")
|
||||||
if action == "restore":
|
if action == "restore":
|
||||||
|
@ -1,44 +0,0 @@
|
|||||||
# Generated by Django 4.2.11 on 2024-04-22 20:44
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("paperless_mail", "0023_remove_mailrule_filter_attachment_filename_and_more"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="mailaccount",
|
|
||||||
name="deleted_at",
|
|
||||||
field=models.DateTimeField(blank=True, null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="mailaccount",
|
|
||||||
name="restored_at",
|
|
||||||
field=models.DateTimeField(blank=True, null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="mailrule",
|
|
||||||
name="deleted_at",
|
|
||||||
field=models.DateTimeField(blank=True, null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="mailrule",
|
|
||||||
name="restored_at",
|
|
||||||
field=models.DateTimeField(blank=True, null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="processedmail",
|
|
||||||
name="deleted_at",
|
|
||||||
field=models.DateTimeField(blank=True, null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="processedmail",
|
|
||||||
name="restored_at",
|
|
||||||
field=models.DateTimeField(blank=True, null=True),
|
|
||||||
),
|
|
||||||
]
|
|
Loading…
x
Reference in New Issue
Block a user