Working removal of custom fields
This commit is contained in:
parent
44728aa048
commit
7879b9e91b
@ -69,7 +69,7 @@ export class CustomFieldsDropdownComponent implements OnDestroy {
|
|||||||
|
|
||||||
private updateUnusedFields() {
|
private updateUnusedFields() {
|
||||||
this.unusedFields = this.customFields.filter(
|
this.unusedFields = this.customFields.filter(
|
||||||
(f) => !this.existingFields.find((e) => e.field.id === f.id)
|
(f) => !this.existingFields?.find((e) => e.field.id === f.id)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,12 @@
|
|||||||
import { Directive, ElementRef, Input, OnInit, ViewChild } from '@angular/core'
|
import {
|
||||||
|
Directive,
|
||||||
|
ElementRef,
|
||||||
|
EventEmitter,
|
||||||
|
Input,
|
||||||
|
OnInit,
|
||||||
|
Output,
|
||||||
|
ViewChild,
|
||||||
|
} from '@angular/core'
|
||||||
import { ControlValueAccessor } from '@angular/forms'
|
import { ControlValueAccessor } from '@angular/forms'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
|
||||||
@ -41,9 +49,18 @@ export class AbstractInputComponent<T> implements OnInit, ControlValueAccessor {
|
|||||||
@Input()
|
@Input()
|
||||||
error: string
|
error: string
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
hint: string
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
horizontal: boolean = false
|
horizontal: boolean = false
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
removable: boolean = false
|
||||||
|
|
||||||
|
@Output()
|
||||||
|
removed: EventEmitter<AbstractInputComponent<any>> = new EventEmitter()
|
||||||
|
|
||||||
value: T
|
value: T
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
@ -51,7 +68,4 @@ export class AbstractInputComponent<T> implements OnInit, ControlValueAccessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
inputId: string
|
inputId: string
|
||||||
|
|
||||||
@Input()
|
|
||||||
hint: string
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,12 @@
|
|||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="d-flex align-items-center" [class.col-md-3]="horizontal">
|
<div class="d-flex align-items-center position-relative hidden-button-container" [class.col-md-3]="horizontal">
|
||||||
<label class="form-label mb-md-0" [for]="inputId">{{title}}</label>
|
<label class="form-label mb-md-0" [for]="inputId">{{title}}</label>
|
||||||
|
<button type="button" *ngIf="removable" class="btn btn-sm btn-danger position-absolute left-0" (click)="removed.emit(this)">
|
||||||
|
<svg class="sidebaricon" fill="currentColor">
|
||||||
|
<use xlink:href="assets/bootstrap-icons.svg#x"/>
|
||||||
|
</svg> <ng-container i18n>Remove</ng-container>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div [class.col-md-9]="horizontal">
|
<div [class.col-md-9]="horizontal">
|
||||||
<div class="input-group" [class.is-invalid]="error">
|
<div class="input-group" [class.is-invalid]="error">
|
||||||
|
@ -1,7 +1,12 @@
|
|||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="d-flex align-items-center" [class.col-md-3]="horizontal">
|
<div class="d-flex align-items-center position-relative hidden-button-container" [class.col-md-3]="horizontal">
|
||||||
<label class="form-label mb-md-0" [for]="inputId">{{title}}</label>
|
<label class="form-label mb-md-0" [for]="inputId">{{title}}</label>
|
||||||
|
<button type="button" *ngIf="removable" class="btn btn-sm btn-danger position-absolute left-0" (click)="removed.emit(this)">
|
||||||
|
<svg class="sidebaricon" fill="currentColor">
|
||||||
|
<use xlink:href="assets/bootstrap-icons.svg#x"/>
|
||||||
|
</svg> <ng-container i18n>Remove</ng-container>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div [class.col-md-9]="horizontal">
|
<div [class.col-md-9]="horizontal">
|
||||||
<div class="input-group" [class.is-invalid]="error">
|
<div class="input-group" [class.is-invalid]="error">
|
||||||
|
@ -1,7 +1,12 @@
|
|||||||
<div class="mb-3 paperless-input-select" [class.disabled]="disabled">
|
<div class="mb-3 paperless-input-select" [class.disabled]="disabled">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="d-flex align-items-center" [class.col-md-3]="horizontal">
|
<div class="d-flex align-items-center position-relative hidden-button-container" [class.col-md-3]="horizontal">
|
||||||
<label *ngIf="title" class="form-label mb-md-0" [for]="inputId">{{title}}</label>
|
<label *ngIf="title" class="form-label mb-md-0" [for]="inputId">{{title}}</label>
|
||||||
|
<button type="button" *ngIf="removable" class="btn btn-sm btn-danger position-absolute left-0" (click)="removed.emit(this)">
|
||||||
|
<svg class="sidebaricon" fill="currentColor">
|
||||||
|
<use xlink:href="assets/bootstrap-icons.svg#x"/>
|
||||||
|
</svg> <ng-container i18n>Remove</ng-container>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div [class.col-md-9]="horizontal">
|
<div [class.col-md-9]="horizontal">
|
||||||
<div [class.input-group]="allowCreateNew || showFilter">
|
<div [class.input-group]="allowCreateNew || showFilter">
|
||||||
|
@ -1,7 +1,12 @@
|
|||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="d-flex align-items-center" [class.col-md-3]="horizontal">
|
<div class="d-flex align-items-center position-relative hidden-button-container" [class.col-md-3]="horizontal">
|
||||||
<label class="form-label mb-md-0" [for]="inputId">{{title}}</label>
|
<label class="form-label mb-md-0" [for]="inputId">{{title}}</label>
|
||||||
|
<button type="button" *ngIf="removable" class="btn btn-sm btn-danger position-absolute left-0" (click)="removed.emit(this)">
|
||||||
|
<svg class="sidebaricon" fill="currentColor">
|
||||||
|
<use xlink:href="assets/bootstrap-icons.svg#x"/>
|
||||||
|
</svg> <ng-container i18n>Remove</ng-container>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div [class.col-md-9]="horizontal">
|
<div [class.col-md-9]="horizontal">
|
||||||
<input #inputField type="text" class="form-control" [class.is-invalid]="error" [id]="inputId" [(ngModel)]="value" (change)="onChange(value)" [disabled]="disabled">
|
<input #inputField type="text" class="form-control" [class.is-invalid]="error" [id]="inputId" [(ngModel)]="value" (change)="onChange(value)" [disabled]="disabled">
|
||||||
|
@ -51,7 +51,7 @@
|
|||||||
<pngx-custom-fields-dropdown
|
<pngx-custom-fields-dropdown
|
||||||
[documentId]="documentId"
|
[documentId]="documentId"
|
||||||
[disabled]="!userIsOwner"
|
[disabled]="!userIsOwner"
|
||||||
[existingFields]="customFields"
|
[existingFields]="document?.custom_fields"
|
||||||
(added)="addField($event)">
|
(added)="addField($event)">
|
||||||
</pngx-custom-fields-dropdown>
|
</pngx-custom-fields-dropdown>
|
||||||
|
|
||||||
@ -100,11 +100,11 @@
|
|||||||
<pngx-input-select [items]="storagePaths" i18n-title title="Storage path" formControlName="storage_path" [allowNull]="true" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event)"
|
<pngx-input-select [items]="storagePaths" i18n-title title="Storage path" formControlName="storage_path" [allowNull]="true" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event)"
|
||||||
(createNew)="createStoragePath($event)" [suggestions]="suggestions?.storage_paths" i18n-placeholder placeholder="Default" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.StoragePath }"></pngx-input-select>
|
(createNew)="createStoragePath($event)" [suggestions]="suggestions?.storage_paths" i18n-placeholder placeholder="Default" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.StoragePath }"></pngx-input-select>
|
||||||
<pngx-input-tags formControlName="tags" [suggestions]="suggestions?.tags" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event)" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Tag }"></pngx-input-tags>
|
<pngx-input-tags formControlName="tags" [suggestions]="suggestions?.tags" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event)" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Tag }"></pngx-input-tags>
|
||||||
<ng-container *ngFor="let fieldInstance of customFields; let i = index">
|
<ng-container *ngFor="let fieldInstance of document?.custom_fields; let i = index">
|
||||||
<div [formGroup]="customFieldFormFields.controls[i]">
|
<div [formGroup]="customFieldFormFields.controls[i]">
|
||||||
<pngx-input-text formControlName="value" *ngIf="fieldInstance.field.data_type === PaperlessCustomFieldDataType.String" [title]="fieldInstance.field.name" [horizontal]="true"></pngx-input-text>
|
<pngx-input-text formControlName="value" *ngIf="fieldInstance.field.data_type === PaperlessCustomFieldDataType.String" [title]="fieldInstance.field.name" [removable]="true" (removed)="removeField($event)" [horizontal]="true"></pngx-input-text>
|
||||||
<pngx-input-date formControlName="value" *ngIf="fieldInstance.field.data_type === PaperlessCustomFieldDataType.Date" [title]="fieldInstance.field.name" [horizontal]="true"></pngx-input-date>
|
<pngx-input-date formControlName="value" *ngIf="fieldInstance.field.data_type === PaperlessCustomFieldDataType.Date" [title]="fieldInstance.field.name" [removable]="true" (removed)="removeField($event)" [horizontal]="true"></pngx-input-date>
|
||||||
<pngx-input-number formControlName="value" *ngIf="fieldInstance.field.data_type === PaperlessCustomFieldDataType.Integer" [title]="fieldInstance.field.name" [horizontal]="true" [showAdd]="false"></pngx-input-number>
|
<pngx-input-number formControlName="value" *ngIf="fieldInstance.field.data_type === PaperlessCustomFieldDataType.Integer" [title]="fieldInstance.field.name" [removable]="true" (removed)="removeField($event)" [horizontal]="true" [showAdd]="false"></pngx-input-number>
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
|
@ -68,6 +68,7 @@ import {
|
|||||||
PaperlessCustomFieldDataType,
|
PaperlessCustomFieldDataType,
|
||||||
} from 'src/app/data/paperless-custom-field'
|
} from 'src/app/data/paperless-custom-field'
|
||||||
import { PaperlessCustomFieldInstance } from 'src/app/data/paperless-custom-field-instance'
|
import { PaperlessCustomFieldInstance } from 'src/app/data/paperless-custom-field-instance'
|
||||||
|
import { AbstractInputComponent } from '../common/input/abstract-input'
|
||||||
|
|
||||||
enum DocumentDetailNavIDs {
|
enum DocumentDetailNavIDs {
|
||||||
Details = 1,
|
Details = 1,
|
||||||
@ -141,7 +142,6 @@ export class DocumentDetailComponent
|
|||||||
ogDate: Date
|
ogDate: Date
|
||||||
|
|
||||||
public readonly PaperlessCustomFieldDataType = PaperlessCustomFieldDataType
|
public readonly PaperlessCustomFieldDataType = PaperlessCustomFieldDataType
|
||||||
customFields: PaperlessCustomFieldInstance[] = []
|
|
||||||
|
|
||||||
@ViewChild('nav') nav: NgbNav
|
@ViewChild('nav') nav: NgbNav
|
||||||
@ViewChild('pdfPreview') set pdfPreview(element) {
|
@ViewChild('pdfPreview') set pdfPreview(element) {
|
||||||
@ -394,7 +394,7 @@ export class DocumentDetailComponent
|
|||||||
updateComponent(doc: PaperlessDocument) {
|
updateComponent(doc: PaperlessDocument) {
|
||||||
this.document = doc
|
this.document = doc
|
||||||
this.requiresPassword = false
|
this.requiresPassword = false
|
||||||
this.customFields = doc.custom_fields
|
// this.customFields = doc.custom_fields.concat([])
|
||||||
this.updateFormForCustomFields()
|
this.updateFormForCustomFields()
|
||||||
this.documentsService
|
this.documentsService
|
||||||
.getMetadata(doc.id)
|
.getMetadata(doc.id)
|
||||||
@ -448,23 +448,6 @@ export class DocumentDetailComponent
|
|||||||
return this.documentForm.get('custom_fields') as FormArray
|
return this.documentForm.get('custom_fields') as FormArray
|
||||||
}
|
}
|
||||||
|
|
||||||
updateFormForCustomFields() {
|
|
||||||
this.customFieldFormFields.clear()
|
|
||||||
this.customFields.forEach((fieldInstance) => {
|
|
||||||
this.customFieldFormFields.push(
|
|
||||||
new FormGroup({
|
|
||||||
field: new FormGroup({
|
|
||||||
id: new FormControl(fieldInstance.field.id),
|
|
||||||
name: new FormControl(fieldInstance.field.name),
|
|
||||||
data_type: new FormControl(fieldInstance.field.data_type),
|
|
||||||
}),
|
|
||||||
value: new FormControl(fieldInstance.value),
|
|
||||||
}),
|
|
||||||
{ emitEvent: false }
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
createDocumentType(newName: string) {
|
createDocumentType(newName: string) {
|
||||||
var modal = this.modalService.open(DocumentTypeEditDialogComponent, {
|
var modal = this.modalService.open(DocumentTypeEditDialogComponent, {
|
||||||
backdrop: 'static',
|
backdrop: 'static',
|
||||||
@ -542,9 +525,8 @@ export class DocumentDetailComponent
|
|||||||
set_permissions: doc.permissions,
|
set_permissions: doc.permissions,
|
||||||
}
|
}
|
||||||
this.title = doc.title
|
this.title = doc.title
|
||||||
this.documentForm.patchValue(doc)
|
|
||||||
this.customFields = doc.custom_fields
|
|
||||||
this.updateFormForCustomFields()
|
this.updateFormForCustomFields()
|
||||||
|
this.documentForm.patchValue(doc)
|
||||||
this.openDocumentService.setDirty(doc, false)
|
this.openDocumentService.setDirty(doc, false)
|
||||||
},
|
},
|
||||||
error: () => {
|
error: () => {
|
||||||
@ -855,13 +837,41 @@ export class DocumentDetailComponent
|
|||||||
this.documentListViewService.quickFilter(filterRules)
|
this.documentListViewService.quickFilter(filterRules)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateFormForCustomFields(emitEvent: boolean = false) {
|
||||||
|
this.customFieldFormFields.clear({ emitEvent: false })
|
||||||
|
this.document.custom_fields.forEach((fieldInstance) => {
|
||||||
|
this.customFieldFormFields.push(
|
||||||
|
new FormGroup({
|
||||||
|
field: new FormGroup({
|
||||||
|
id: new FormControl(fieldInstance.field.id),
|
||||||
|
name: new FormControl(fieldInstance.field.name),
|
||||||
|
data_type: new FormControl(fieldInstance.field.data_type),
|
||||||
|
}),
|
||||||
|
value: new FormControl(fieldInstance.value),
|
||||||
|
}),
|
||||||
|
{ emitEvent }
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
addField(field: PaperlessCustomField) {
|
addField(field: PaperlessCustomField) {
|
||||||
this.customFields.push({
|
this.document.custom_fields.push({
|
||||||
field,
|
field,
|
||||||
value: null,
|
value: null,
|
||||||
document: this.documentId,
|
document: this.documentId,
|
||||||
created: new Date(),
|
created: new Date(),
|
||||||
})
|
})
|
||||||
this.updateFormForCustomFields()
|
this.updateFormForCustomFields(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
removeField(input: AbstractInputComponent<any>) {
|
||||||
|
// ok for now as custom field name unique is a constraint
|
||||||
|
const customFieldIndex = this.document.custom_fields.findIndex(
|
||||||
|
(f) => f.field.name === input.title
|
||||||
|
)
|
||||||
|
if (customFieldIndex) {
|
||||||
|
this.document.custom_fields.splice(customFieldIndex, 1)
|
||||||
|
this.updateFormForCustomFields(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -661,3 +661,17 @@ code {
|
|||||||
.cdk-drag-animating {
|
.cdk-drag-animating {
|
||||||
transition: transform 300ms cubic-bezier(0, 0, 0.2, 1);
|
transition: transform 300ms cubic-bezier(0, 0, 0.2, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hidden-button-container {
|
||||||
|
button {
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: opacity .2s ease;
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
button {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: initial;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -219,6 +219,13 @@ class Migration(migrations.Migration):
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
migrations.AddConstraint(
|
||||||
|
model_name="customfield",
|
||||||
|
constraint=models.UniqueConstraint(
|
||||||
|
fields=("name",),
|
||||||
|
name="documents_customfield_unique_name",
|
||||||
|
),
|
||||||
|
),
|
||||||
migrations.AddConstraint(
|
migrations.AddConstraint(
|
||||||
model_name="customfieldinstance",
|
model_name="customfieldinstance",
|
||||||
constraint=models.UniqueConstraint(
|
constraint=models.UniqueConstraint(
|
||||||
|
@ -916,6 +916,12 @@ class CustomField(models.Model):
|
|||||||
ordering = ("created",)
|
ordering = ("created",)
|
||||||
verbose_name = _("custom field")
|
verbose_name = _("custom field")
|
||||||
verbose_name_plural = _("custom fields")
|
verbose_name_plural = _("custom fields")
|
||||||
|
constraints = [
|
||||||
|
models.UniqueConstraint(
|
||||||
|
fields=["name"],
|
||||||
|
name="%(app_label)s_%(class)s_unique_name",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return f"{self.name} : {self.data_type}"
|
return f"{self.name} : {self.data_type}"
|
||||||
|
@ -464,7 +464,7 @@ class DocumentSerializer(OwnedObjectSerializer, DynamicFieldsModelSerializer):
|
|||||||
field_instance["value"] = data_custom_field["value"]
|
field_instance["value"] = data_custom_field["value"]
|
||||||
return values
|
return values
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance: Document, validated_data):
|
||||||
if "custom_fields" in validated_data:
|
if "custom_fields" in validated_data:
|
||||||
custom_fields = validated_data.pop("custom_fields")
|
custom_fields = validated_data.pop("custom_fields")
|
||||||
for field_data in custom_fields:
|
for field_data in custom_fields:
|
||||||
@ -473,6 +473,19 @@ class DocumentSerializer(OwnedObjectSerializer, DynamicFieldsModelSerializer):
|
|||||||
field=field_data["field"],
|
field=field_data["field"],
|
||||||
value=field_data["value"],
|
value=field_data["value"],
|
||||||
)
|
)
|
||||||
|
existing_fields = CustomFieldInstance.objects.filter(document=instance)
|
||||||
|
for existing_field in existing_fields:
|
||||||
|
if (
|
||||||
|
not len(
|
||||||
|
[
|
||||||
|
f
|
||||||
|
for f in custom_fields
|
||||||
|
if f["field"]["id"] == existing_field.field.id
|
||||||
|
],
|
||||||
|
)
|
||||||
|
> 0
|
||||||
|
):
|
||||||
|
existing_field.delete()
|
||||||
if "created_date" in validated_data and "created" not in validated_data:
|
if "created_date" in validated_data and "created" not in validated_data:
|
||||||
new_datetime = datetime.datetime.combine(
|
new_datetime = datetime.datetime.combine(
|
||||||
validated_data.get("created_date"),
|
validated_data.get("created_date"),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user