mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-05-23 17:52:23 -04:00
Fix: handle created change with api version increment, use created only on frontend, deprecate created_date (#9962)
This commit is contained in:
parent
fae4116504
commit
55917fcabe
@ -418,3 +418,9 @@ Initial API version.
|
|||||||
|
|
||||||
- The user field of document notes now returns a simplified user object
|
- The user field of document notes now returns a simplified user object
|
||||||
rather than just the user ID.
|
rather than just the user ID.
|
||||||
|
|
||||||
|
#### Version 9
|
||||||
|
|
||||||
|
- The document `created` field is now a date, not a datetime. The
|
||||||
|
`created_date` field is considered deprecated and will be removed in a
|
||||||
|
future version.
|
||||||
|
@ -43,7 +43,7 @@
|
|||||||
<a routerLink="/documents/{{doc.id}}" class="btn-link text-dark text-decoration-none py-2 py-md-3" title="Open document" i18n-title>{{doc.added | customDate}}</a>
|
<a routerLink="/documents/{{doc.id}}" class="btn-link text-dark text-decoration-none py-2 py-md-3" title="Open document" i18n-title>{{doc.added | customDate}}</a>
|
||||||
}
|
}
|
||||||
@case (DisplayField.CREATED) {
|
@case (DisplayField.CREATED) {
|
||||||
<a routerLink="/documents/{{doc.id}}" class="btn-link text-dark text-decoration-none py-2 py-md-3" title="Open document" i18n-title>{{doc.created_date | customDate}}</a>
|
<a routerLink="/documents/{{doc.id}}" class="btn-link text-dark text-decoration-none py-2 py-md-3" title="Open document" i18n-title>{{doc.created | customDate}}</a>
|
||||||
}
|
}
|
||||||
@case (DisplayField.TITLE) {
|
@case (DisplayField.TITLE) {
|
||||||
<a routerLink="/documents/{{doc.id}}" title="Open document" i18n-title class="btn-link text-dark text-decoration-none py-2 py-md-3">{{doc.title | documentTitle}}</a>
|
<a routerLink="/documents/{{doc.id}}" title="Open document" i18n-title class="btn-link text-dark text-decoration-none py-2 py-md-3">{{doc.title | documentTitle}}</a>
|
||||||
|
@ -129,8 +129,8 @@
|
|||||||
<div>
|
<div>
|
||||||
<pngx-input-text #inputTitle i18n-title title="Title" formControlName="title" [horizontal]="true" (keyup)="titleKeyUp($event)" [error]="error?.title"></pngx-input-text>
|
<pngx-input-text #inputTitle i18n-title title="Title" formControlName="title" [horizontal]="true" (keyup)="titleKeyUp($event)" [error]="error?.title"></pngx-input-text>
|
||||||
<pngx-input-number i18n-title title="Archive serial number" [error]="error?.archive_serial_number" [horizontal]="true" formControlName='archive_serial_number'></pngx-input-number>
|
<pngx-input-number i18n-title title="Archive serial number" [error]="error?.archive_serial_number" [horizontal]="true" formControlName='archive_serial_number'></pngx-input-number>
|
||||||
<pngx-input-date i18n-title title="Date created" formControlName="created_date" [suggestions]="suggestions?.dates" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event)"
|
<pngx-input-date i18n-title title="Date created" formControlName="created" [suggestions]="suggestions?.dates" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event)"
|
||||||
[error]="error?.created_date"></pngx-input-date>
|
[error]="error?.created"></pngx-input-date>
|
||||||
<pngx-input-select [items]="correspondents" i18n-title title="Correspondent" formControlName="correspondent" [allowNull]="true" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event, DataType.Correspondent)"
|
<pngx-input-select [items]="correspondents" i18n-title title="Correspondent" formControlName="correspondent" [allowNull]="true" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event, DataType.Correspondent)"
|
||||||
(createNew)="createCorrespondent($event)" [hideAddButton]="createDisabled(DataType.Correspondent)" [suggestions]="suggestions?.correspondents" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Correspondent }"></pngx-input-select>
|
(createNew)="createCorrespondent($event)" [hideAddButton]="createDisabled(DataType.Correspondent)" [suggestions]="suggestions?.correspondents" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Correspondent }"></pngx-input-select>
|
||||||
<pngx-input-select [items]="documentTypes" i18n-title title="Document type" formControlName="document_type" [allowNull]="true" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event, DataType.DocumentType)"
|
<pngx-input-select [items]="documentTypes" i18n-title title="Document type" formControlName="document_type" [allowNull]="true" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event, DataType.DocumentType)"
|
||||||
|
@ -208,7 +208,7 @@ export class DocumentDetailComponent
|
|||||||
documentForm: FormGroup = new FormGroup({
|
documentForm: FormGroup = new FormGroup({
|
||||||
title: new FormControl(''),
|
title: new FormControl(''),
|
||||||
content: new FormControl(''),
|
content: new FormControl(''),
|
||||||
created_date: new FormControl(),
|
created: new FormControl(),
|
||||||
correspondent: new FormControl(),
|
correspondent: new FormControl(),
|
||||||
document_type: new FormControl(),
|
document_type: new FormControl(),
|
||||||
storage_path: new FormControl(),
|
storage_path: new FormControl(),
|
||||||
@ -490,7 +490,7 @@ export class DocumentDetailComponent
|
|||||||
this.store = new BehaviorSubject({
|
this.store = new BehaviorSubject({
|
||||||
title: doc.title,
|
title: doc.title,
|
||||||
content: doc.content,
|
content: doc.content,
|
||||||
created_date: doc.created_date,
|
created: doc.created,
|
||||||
correspondent: doc.correspondent,
|
correspondent: doc.correspondent,
|
||||||
document_type: doc.document_type,
|
document_type: doc.document_type,
|
||||||
storage_path: doc.storage_path,
|
storage_path: doc.storage_path,
|
||||||
|
@ -112,14 +112,14 @@
|
|||||||
@if (displayFields.includes(DisplayField.CREATED) || displayFields.includes(DisplayField.ADDED)) {
|
@if (displayFields.includes(DisplayField.CREATED) || displayFields.includes(DisplayField.ADDED)) {
|
||||||
<ng-template #dateTooltip>
|
<ng-template #dateTooltip>
|
||||||
<div class="d-flex flex-column text-light">
|
<div class="d-flex flex-column text-light">
|
||||||
<span i18n>Created: {{ document.created_date | customDate }}</span>
|
<span i18n>Created: {{ document.created | customDate }}</span>
|
||||||
<span i18n>Added: {{ document.added | customDate }}</span>
|
<span i18n>Added: {{ document.added | customDate }}</span>
|
||||||
<span i18n>Modified: {{ document.modified | customDate }}</span>
|
<span i18n>Modified: {{ document.modified | customDate }}</span>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
@if (displayFields.includes(DisplayField.CREATED)) {
|
@if (displayFields.includes(DisplayField.CREATED)) {
|
||||||
<div class="list-group-item bg-light text-dark p-1 border-0 d-flex align-items-center" [ngbTooltip]="dateTooltip">
|
<div class="list-group-item bg-light text-dark p-1 border-0 d-flex align-items-center" [ngbTooltip]="dateTooltip">
|
||||||
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="calendar-event"></i-bs><small>{{document.created_date | customDate:'mediumDate'}}</small>
|
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="calendar-event"></i-bs><small>{{document.created | customDate:'mediumDate'}}</small>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@if (displayFields.includes(DisplayField.ADDED)) {
|
@if (displayFields.includes(DisplayField.ADDED)) {
|
||||||
|
@ -73,14 +73,14 @@
|
|||||||
<div class="list-group-item bg-transparent p-0 border-0 d-flex flex-wrap-reverse justify-content-between">
|
<div class="list-group-item bg-transparent p-0 border-0 d-flex flex-wrap-reverse justify-content-between">
|
||||||
<ng-template #dateTooltip>
|
<ng-template #dateTooltip>
|
||||||
<div class="d-flex flex-column text-light">
|
<div class="d-flex flex-column text-light">
|
||||||
<span i18n>Created: {{ document.created_date | customDate }}</span>
|
<span i18n>Created: {{ document.created | customDate }}</span>
|
||||||
<span i18n>Added: {{ document.added | customDate }}</span>
|
<span i18n>Added: {{ document.added | customDate }}</span>
|
||||||
<span i18n>Modified: {{ document.modified | customDate }}</span>
|
<span i18n>Modified: {{ document.modified | customDate }}</span>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<div class="ps-0 p-1" placement="top" [ngbTooltip]="dateTooltip">
|
<div class="ps-0 p-1" placement="top" [ngbTooltip]="dateTooltip">
|
||||||
<i-bs width="1em" height="1em" class="me-2 text-muted" name="calendar-event"></i-bs>
|
<i-bs width="1em" height="1em" class="me-2 text-muted" name="calendar-event"></i-bs>
|
||||||
<small>{{document.created_date | customDate:'mediumDate'}}</small>
|
<small>{{document.created | customDate:'mediumDate'}}</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
@ -348,7 +348,7 @@
|
|||||||
}
|
}
|
||||||
@if (activeDisplayFields.includes(DisplayField.CREATED)) {
|
@if (activeDisplayFields.includes(DisplayField.CREATED)) {
|
||||||
<td>
|
<td>
|
||||||
{{d.created_date | customDate}}
|
{{d.created | customDate}}
|
||||||
</td>
|
</td>
|
||||||
}
|
}
|
||||||
@if (activeDisplayFields.includes(DisplayField.ADDED)) {
|
@if (activeDisplayFields.includes(DisplayField.ADDED)) {
|
||||||
|
@ -130,9 +130,6 @@ export interface Document extends ObjectWithPermissions {
|
|||||||
// UTC
|
// UTC
|
||||||
created?: Date
|
created?: Date
|
||||||
|
|
||||||
// localized date
|
|
||||||
created_date?: Date
|
|
||||||
|
|
||||||
modified?: Date
|
modified?: Date
|
||||||
|
|
||||||
added?: Date
|
added?: Date
|
||||||
|
@ -190,8 +190,6 @@ export class DocumentService extends AbstractPaperlessService<Document> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
patch(o: Document): Observable<Document> {
|
patch(o: Document): Observable<Document> {
|
||||||
// we want to only set created_date
|
|
||||||
delete o.created
|
|
||||||
o.remove_inbox_tags = !!this.settingsService.get(
|
o.remove_inbox_tags = !!this.settingsService.get(
|
||||||
SETTINGS_KEYS.DOCUMENT_EDITING_REMOVE_INBOX_TAGS
|
SETTINGS_KEYS.DOCUMENT_EDITING_REMOVE_INBOX_TAGS
|
||||||
)
|
)
|
||||||
|
@ -3,7 +3,7 @@ const base_url = new URL(document.baseURI)
|
|||||||
export const environment = {
|
export const environment = {
|
||||||
production: true,
|
production: true,
|
||||||
apiBaseUrl: document.baseURI + 'api/',
|
apiBaseUrl: document.baseURI + 'api/',
|
||||||
apiVersion: '8', // match src/paperless/settings.py
|
apiVersion: '9', // match src/paperless/settings.py
|
||||||
appTitle: 'Paperless-ngx',
|
appTitle: 'Paperless-ngx',
|
||||||
version: '2.15.3',
|
version: '2.15.3',
|
||||||
webSocketHost: window.location.host,
|
webSocketHost: window.location.host,
|
||||||
|
@ -21,6 +21,7 @@ from django.utils.crypto import get_random_string
|
|||||||
from django.utils.text import slugify
|
from django.utils.text import slugify
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
from drf_spectacular.utils import extend_schema_field
|
from drf_spectacular.utils import extend_schema_field
|
||||||
|
from drf_spectacular.utils import extend_schema_serializer
|
||||||
from drf_writable_nested.serializers import NestedUpdateMixin
|
from drf_writable_nested.serializers import NestedUpdateMixin
|
||||||
from guardian.core import ObjectPermissionChecker
|
from guardian.core import ObjectPermissionChecker
|
||||||
from guardian.shortcuts import get_users_with_perms
|
from guardian.shortcuts import get_users_with_perms
|
||||||
@ -891,6 +892,9 @@ class NotesSerializer(serializers.ModelSerializer):
|
|||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
@extend_schema_serializer(
|
||||||
|
deprecate_fields=["created_date"],
|
||||||
|
)
|
||||||
class DocumentSerializer(
|
class DocumentSerializer(
|
||||||
OwnedObjectSerializer,
|
OwnedObjectSerializer,
|
||||||
NestedUpdateMixin,
|
NestedUpdateMixin,
|
||||||
@ -943,6 +947,22 @@ class DocumentSerializer(
|
|||||||
doc = super().to_representation(instance)
|
doc = super().to_representation(instance)
|
||||||
if self.truncate_content and "content" in self.fields:
|
if self.truncate_content and "content" in self.fields:
|
||||||
doc["content"] = doc.get("content")[0:550]
|
doc["content"] = doc.get("content")[0:550]
|
||||||
|
|
||||||
|
request = self.context.get("request")
|
||||||
|
api_version = int(
|
||||||
|
request.version if request else settings.REST_FRAMEWORK["DEFAULT_VERSION"],
|
||||||
|
)
|
||||||
|
|
||||||
|
if api_version < 9:
|
||||||
|
# provide created as a datetime for backwards compatibility
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
doc["created"] = timezone.make_aware(
|
||||||
|
datetime.combine(
|
||||||
|
instance.created,
|
||||||
|
datetime.min.time(),
|
||||||
|
),
|
||||||
|
).isoformat()
|
||||||
return doc
|
return doc
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
@ -968,6 +988,9 @@ class DocumentSerializer(
|
|||||||
instance.created = validated_data.get("created_date")
|
instance.created = validated_data.get("created_date")
|
||||||
instance.save()
|
instance.save()
|
||||||
if "created_date" in validated_data:
|
if "created_date" in validated_data:
|
||||||
|
logger.warning(
|
||||||
|
"created_date is deprecated, use created instead",
|
||||||
|
)
|
||||||
validated_data.pop("created_date")
|
validated_data.pop("created_date")
|
||||||
if instance.custom_fields.count() > 0 and "custom_fields" in validated_data:
|
if instance.custom_fields.count() > 0 and "custom_fields" in validated_data:
|
||||||
incoming_custom_fields = [
|
incoming_custom_fields = [
|
||||||
|
@ -171,6 +171,38 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase):
|
|||||||
results = response.data["results"]
|
results = response.data["results"]
|
||||||
self.assertEqual(len(results[0]), 0)
|
self.assertEqual(len(results[0]), 0)
|
||||||
|
|
||||||
|
def test_document_legacy_created_format(self):
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- Existing document
|
||||||
|
WHEN:
|
||||||
|
- Document is requested with api version ≥ 9
|
||||||
|
- Document is requested with api version < 9
|
||||||
|
THEN:
|
||||||
|
- Document created field is returned as date
|
||||||
|
- Document created field is returned as datetime
|
||||||
|
"""
|
||||||
|
doc = Document.objects.create(
|
||||||
|
title="none",
|
||||||
|
checksum="123",
|
||||||
|
mime_type="application/pdf",
|
||||||
|
created=date(2023, 1, 1),
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self.client.get(
|
||||||
|
f"/api/documents/{doc.pk}/",
|
||||||
|
headers={"Accept": "application/json; version=8"},
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
self.assertRegex(response.data["created"], r"^2023-01-01T00:00:00.*$")
|
||||||
|
|
||||||
|
response = self.client.get(
|
||||||
|
f"/api/documents/{doc.pk}/",
|
||||||
|
headers={"Accept": "application/json; version=9"},
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
self.assertEqual(response.data["created"], "2023-01-01")
|
||||||
|
|
||||||
def test_document_update_with_created_date(self):
|
def test_document_update_with_created_date(self):
|
||||||
"""
|
"""
|
||||||
GIVEN:
|
GIVEN:
|
||||||
|
@ -342,10 +342,10 @@ REST_FRAMEWORK = {
|
|||||||
"rest_framework.authentication.SessionAuthentication",
|
"rest_framework.authentication.SessionAuthentication",
|
||||||
],
|
],
|
||||||
"DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.AcceptHeaderVersioning",
|
"DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.AcceptHeaderVersioning",
|
||||||
"DEFAULT_VERSION": "8", # match src-ui/src/environments/environment.prod.ts
|
"DEFAULT_VERSION": "9", # match src-ui/src/environments/environment.prod.ts
|
||||||
# Make sure these are ordered and that the most recent version appears
|
# Make sure these are ordered and that the most recent version appears
|
||||||
# last. See api.md#api-versioning when adding new versions.
|
# last. See api.md#api-versioning when adding new versions.
|
||||||
"ALLOWED_VERSIONS": ["1", "2", "3", "4", "5", "6", "7", "8"],
|
"ALLOWED_VERSIONS": ["1", "2", "3", "4", "5", "6", "7", "8", "9"],
|
||||||
# DRF Spectacular default schema
|
# DRF Spectacular default schema
|
||||||
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
|
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user